diff --git a/BUILD.gn b/BUILD.gn index 17714a67b954c88809b38a12c24e50c197d80ca2..b84de72e89422073d26d7f3e07ac38ef51702ca1 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -142,6 +142,8 @@ group("mmi_tests") { "frameworks/proxy:PointerEventTest", "frameworks/proxy:ut-mmi-proxy-out", "libudev:test", + "service:TwoFingerLongTouchTest", + "service:event_resample_test", "service:mmi-service-tests", "service:rust_mmi_test", "service:ut-mmi-service-out", diff --git a/service/BUILD.gn b/service/BUILD.gn index 4ababa5094acca85f2298fc2ee35069c02131857..96576b5f965898eedbed1dc4a1d98e152eba356d 100644 --- a/service/BUILD.gn +++ b/service/BUILD.gn @@ -53,6 +53,7 @@ config("libmmi_server_config") { "${mmi_service_path}/connect_manager/include", "${mmi_service_path}/filter/include", "${mmi_service_path}/module_loader/include", + "${mmi_service_path}/event_resample/include", "${mmi_path}/interfaces/native/innerkits/proxy/include", "${mmi_path}/interfaces/native/innerkits/event/include", "mouse_event_normalize/include", @@ -135,6 +136,7 @@ ohos_shared_library("libmmi-server") { } if (input_feature_touchscreen) { sources += [ + "event_resample/src/event_resample.cpp", "touch_event_normalize/src/tablet_tool_tranform_processor.cpp", "touch_event_normalize/src/touch_transform_processor.cpp", ] @@ -294,6 +296,65 @@ ohos_unittest("ut-mmi-service-out") { ] } +ohos_unittest("event_resample_test") { + module_out_path = module_output_path + include_dirs = [ "${mmi_path}/service/event_resample/include" ] + + sources = [ + "event_resample/src/event_resample.cpp", + "event_resample/test/event_resample_test.cpp", + ] + + configs = [ ":libmmi_server_config" ] + + deps = [ + "${mmi_path}/patch/diff_libinput_mmi:libinput-third-mmi", + "${mmi_path}/service:libmmi-server", + "${mmi_path}/util:libmmi-util", + "//third_party/cJSON:cjson", + "//third_party/googletest:gmock_main", + "//third_party/googletest:gtest_main", + ] + + external_deps = [ + "c_utils:utils", + "config_policy:configpolicy_util", + "hilog:libhilog", + "preferences:native_preferences", + ] +} + +ohos_unittest("TwoFingerLongTouchTest") { + module_out_path = module_output_path + include_dirs = [ "${mmi_path}/service/key_command/test" ] + + cflags = [ "-DUNIT_TEST" ] + + sources = [ + "key_command/src/key_command_handler.cpp", + "key_command/test/ability_manager_client_stub.cpp", + "key_command/test/two_finger_long_touch_test.cpp", + ] + + configs = [ ":libmmi_server_config" ] + + deps = [ + "${mmi_path}/patch/diff_libinput_mmi:libinput-third-mmi", + "${mmi_path}/service:libmmi-server", + "${mmi_path}/util:libmmi-util", + "//third_party/cJSON:cjson", + "//third_party/googletest:gmock_main", + "//third_party/googletest:gtest_main", + ] + + external_deps = [ + "c_utils:utils", + "config_policy:configpolicy_util", + "hilog:libhilog", + "preferences:native_preferences", + ] +} + ohos_unittest("TransformPointTest") { module_out_path = module_output_path diff --git a/service/event_handler/include/event_normalize_handler.h b/service/event_handler/include/event_normalize_handler.h index 177604448b6226cc9da368831a5de649b6f023b1..2e163af8b3d29f514730c9c234f031c0e5d2ce8e 100644 --- a/service/event_handler/include/event_normalize_handler.h +++ b/service/event_handler/include/event_normalize_handler.h @@ -26,7 +26,7 @@ class EventNormalizeHandler : public IInputEventHandler { public: EventNormalizeHandler() = default; ~EventNormalizeHandler() = default; - void HandleEvent(libinput_event* event); + void HandleEvent(libinput_event* event, int64_t frameTime); #ifdef OHOS_BUILD_ENABLE_KEYBOARD void HandleKeyEvent(const std::shared_ptr keyEvent) override; #endif // OHOS_BUILD_ENABLE_KEYBOARD @@ -45,7 +45,7 @@ private: int32_t HandleTouchPadEvent(libinput_event* event); int32_t HandleGestureEvent(libinput_event* event); int32_t HandleMouseEvent(libinput_event* event); - int32_t HandleTouchEvent(libinput_event* event); + int32_t HandleTouchEvent(libinput_event* event, int64_t frameTime); int32_t HandleSwitchInputEvent(libinput_event* event); int32_t HandleTableToolEvent(libinput_event* event); int32_t HandleJoystickEvent(libinput_event* event); diff --git a/service/event_handler/include/input_event_handler.h b/service/event_handler/include/input_event_handler.h index edf02f72e779e18eca2267e3120e183cba6be95c..6ba235fb702004bb6bd7b679a1fefd918de568c4 100644 --- a/service/event_handler/include/input_event_handler.h +++ b/service/event_handler/include/input_event_handler.h @@ -41,7 +41,7 @@ class InputEventHandler final { public: DISALLOW_COPY_AND_MOVE(InputEventHandler); void Init(UDSServer& udsServer); - void OnEvent(void *event); + void OnEvent(void *event, int64_t frameTime); UDSServer *GetUDSServer() const; std::shared_ptr GetEventNormalizeHandler() const; diff --git a/service/event_handler/src/event_normalize_handler.cpp b/service/event_handler/src/event_normalize_handler.cpp index 59f66111e3152e0173f735dfa6e3f6db6b257fb6..2ba872fc5b8abac1a99c2a416037e122980085f0 100644 --- a/service/event_handler/src/event_normalize_handler.cpp +++ b/service/event_handler/src/event_normalize_handler.cpp @@ -31,6 +31,7 @@ #include "time_cost_chk.h" #include "timer_manager.h" #include "touch_event_normalize.h" +#include "event_resample.h" namespace OHOS { namespace MMI { @@ -40,9 +41,24 @@ constexpr int32_t FINGER_NUM = 2; constexpr int32_t MT_TOOL_PALM = 2; } -void EventNormalizeHandler::HandleEvent(libinput_event* event) +void EventNormalizeHandler::HandleEvent(libinput_event* event, int64_t frameTime) { CALL_DEBUG_ENTER; + + if (event == nullptr) { + std::shared_ptr pointerEvent = EVENT_RESAMPLE_HDR->getPointerEvent(); + if (pointerEvent != nullptr) { + switch (pointerEvent->GetSourceType()) { + case PointerEvent::SOURCE_TYPE_TOUCHSCREEN: + HandleTouchEvent(event, frameTime); + break; + default: + return; + } + } + return; + } + CHKPV(event); DfxHisysevent::GetDispStartTime(); auto type = libinput_event_get_type(event); @@ -96,7 +112,7 @@ void EventNormalizeHandler::HandleEvent(libinput_event* event) case LIBINPUT_EVENT_TOUCH_DOWN: case LIBINPUT_EVENT_TOUCH_UP: case LIBINPUT_EVENT_TOUCH_MOTION: { - HandleTouchEvent(event); + HandleTouchEvent(event, frameTime); DfxHisysevent::CalcPointerDispTimes(); break; } @@ -352,19 +368,51 @@ int32_t EventNormalizeHandler::HandleGestureEvent(libinput_event* event) return RET_OK; } -int32_t EventNormalizeHandler::HandleTouchEvent(libinput_event* event) +int32_t EventNormalizeHandler::HandleTouchEvent(libinput_event* event, int64_t frameTime) { if (nextHandler_ == nullptr) { MMI_HILOGW("Touchscreen device does not support"); return ERROR_UNSUPPORT; } #ifdef OHOS_BUILD_ENABLE_TOUCH - CHKPR(event, ERROR_NULL_POINTER); - auto pointerEvent = TouchEventHdr->OnLibInput(event, TouchEventNormalize::DeviceType::TOUCH); - CHKPR(pointerEvent, ERROR_NULL_POINTER); - BytraceAdapter::StartBytrace(pointerEvent, BytraceAdapter::TRACE_START); - nextHandler_->HandleTouchEvent(pointerEvent); - ResetTouchUpEvent(pointerEvent, event); + std::shared_ptr pointerEvent = nullptr; + if (event != nullptr) { + CHKPR(event, ERROR_NULL_POINTER); + pointerEvent = TouchEventHdr->OnLibInput(event, TouchEventNormalize::DeviceType::TOUCH); + CHKPR(pointerEvent, ERROR_NULL_POINTER); + } + + bool deferred = false; + ErrCode status = RET_OK; + std::shared_ptr outputEvent = EVENT_RESAMPLE_HDR->onEventConsume(pointerEvent, frameTime, + deferred, status); + if ((outputEvent == nullptr) && (deferred == false)) { + MMI_HILOGD("NULL output event received: %{public}d %{public}d", deferred, status); + return RET_OK; + } else { + MMI_HILOGD("Output event received: %{public}d %{public}d %{public}d %{public}d", + outputEvent->GetSourceType(), outputEvent->GetPointerAction(), deferred, status); + pointerEvent = outputEvent; + } + + if (pointerEvent != nullptr) { + BytraceAdapter::StartBytrace(pointerEvent, BytraceAdapter::TRACE_START); + nextHandler_->HandleTouchEvent(pointerEvent); + } + + if (deferred == true) { + pointerEvent = EVENT_RESAMPLE_HDR->onEventConsume(NULL, frameTime, deferred, status); + if (pointerEvent != nullptr) { + MMI_HILOGD("Deferred event received: %{public}d %{public}d %{public}d %{public}d", + pointerEvent->GetSourceType(), pointerEvent->GetPointerAction(), deferred, status); + BytraceAdapter::StartBytrace(pointerEvent, BytraceAdapter::TRACE_START); + nextHandler_->HandleTouchEvent(pointerEvent); + } + } + + if (pointerEvent != nullptr) { + ResetTouchUpEvent(pointerEvent, event); + } #else MMI_HILOGW("Touchscreen device does not support"); #endif // OHOS_BUILD_ENABLE_TOUCH diff --git a/service/event_handler/src/input_event_handler.cpp b/service/event_handler/src/input_event_handler.cpp index 7173488a8d58e7124a490f55972cbb03a25190b6..eed5329023facbeccbc0a8e0db1b638f9eaaf960 100644 --- a/service/event_handler/src/input_event_handler.cpp +++ b/service/event_handler/src/input_event_handler.cpp @@ -48,8 +48,14 @@ void InputEventHandler::Init(UDSServer& udsServer) BuildInputHandlerChain(); } -void InputEventHandler::OnEvent(void *event) +void InputEventHandler::OnEvent(void *event, int64_t frameTime) { + CHKPV(eventNormalizeHandler_); + if (event == nullptr) { + eventNormalizeHandler_->HandleEvent(nullptr, frameTime); + return; + } + CHKPV(event); idSeed_ += 1; const uint64_t maxUInt64 = (std::numeric_limits::max)() - 1; @@ -64,8 +70,7 @@ void InputEventHandler::OnEvent(void *event) int64_t beginTime = GetSysClockTime(); MMI_HILOGD("Event reporting. id:%{public}" PRId64 ",tid:%{public}" PRId64 ",eventType:%{public}d," "beginTime:%{public}" PRId64, idSeed_, GetThisThreadId(), eventType, beginTime); - CHKPV(eventNormalizeHandler_); - eventNormalizeHandler_->HandleEvent(lpEvent); + eventNormalizeHandler_->HandleEvent(lpEvent, frameTime); int64_t endTime = GetSysClockTime(); int64_t lostTime = endTime - beginTime; MMI_HILOGD("Event handling completed. id:%{public}" PRId64 ",endTime:%{public}" PRId64 diff --git a/service/event_resample/include/event_resample.h b/service/event_resample/include/event_resample.h new file mode 100644 index 0000000000000000000000000000000000000000..1c1f60d8d3e18a0e47785dcc559159da3985b6f7 --- /dev/null +++ b/service/event_resample/include/event_resample.h @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2021-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. + */ + +#ifndef EVENT_RESAMPLE_H +#define EVENT_RESAMPLE_H + +#include +#include +#include + +#include "proto.h" +#include "singleton.h" +#include "nocopyable.h" +#include "error_multimodal.h" +#include "pointer_event.h" + +namespace OHOS { +namespace MMI { + +constexpr uint32_t MIN_HISTORY_SIZE = 2; + +class EventResample final { + DECLARE_DELAYED_SINGLETON(EventResample); + +public: + DISALLOW_COPY_AND_MOVE(EventResample); + std::shared_ptr onEventConsume(std::shared_ptr pointerEvent, int64_t frameTime, + bool &deferred, ErrCode &status); + std::shared_ptr getPointerEvent(); + + // Microseconds per milliseconds. + static constexpr int64_t US_PER_MS = 1000; + + // Latency added during resampling. A few milliseconds doesn't hurt much but + // reduces the impact of mispredicted touch positions. + static constexpr int64_t RESAMPLE_LATENCY = 5 * US_PER_MS; + + // Minimum time difference between consecutive samples before attempting to resample. + static constexpr int64_t RESAMPLE_MIN_DELTA = 2 * US_PER_MS; + + // Maximum time difference between consecutive samples before attempting to resample + // by extrapolation. + static constexpr int64_t RESAMPLE_MAX_DELTA = 20 * US_PER_MS; + + // Maximum time to predict forward from the last known state, to avoid predicting too + // far into the future. This time is further bounded by 50% of the last time delta. + static constexpr int64_t RESAMPLE_MAX_PREDICTION = 4 * US_PER_MS; + +private: + + struct Pointer { + int32_t coordX; + int32_t coordY; + int32_t toolType; + int32_t id; + + void copyFrom(const Pointer& other) + { + coordX = other.coordX; + coordY = other.coordY; + toolType = other.toolType; + id = other.id; + } + + void reset() + { + coordX = 0; + coordY = 0; + toolType = 0; + id = 0; + } + }; + + struct MotionEvent { + std::map pointers; + int64_t actionTime { 0 }; + uint32_t pointerCount { 0 }; + int32_t sourceType { PointerEvent::SOURCE_TYPE_UNKNOWN }; + int32_t pointerAction { PointerEvent::POINTER_ACTION_UNKNOWN }; + int32_t deviceId { 0 }; + + void reset() + { + pointers.clear(); + actionTime = 0; + pointerCount = 0; + sourceType = PointerEvent::SOURCE_TYPE_UNKNOWN; + pointerAction = PointerEvent::POINTER_ACTION_UNKNOWN; + deviceId = 0; + } + + void initializeFrom(MotionEvent& other) + { + for (auto &it : other.pointers) { + pointers[it.first] = it.second; + } + actionTime = other.actionTime; + pointerCount = other.pointerCount; + deviceId = other.deviceId; + sourceType = other.sourceType; + pointerAction = other.pointerAction; + } + + void initializeFrom(std::shared_ptr event) + { + actionTime = event->GetActionTime(); + deviceId = event->GetDeviceId(); + sourceType = event->GetSourceType(); + pointerAction = event->GetPointerAction(); + + std::vector pointerIds = event->GetPointerIds(); + pointerCount = 0; + for (auto &it : pointerIds) { + PointerEvent::PointerItem item; + if (event->GetPointerItem(it, item)) { + Pointer pointer; + pointer.coordX = item.GetDisplayX(); + pointer.coordY = item.GetDisplayY(); + pointer.toolType = item.GetToolType(); + pointer.id = item.GetPointerId(); + pointers[pointer.id] = pointer; + pointerCount++; + } + } + } + }; + + struct Batch { + std::vector samples; + }; + std::vector batches_; + + struct History { + std::map pointers; + int64_t actionTime { 0 }; + + void initializeFrom(const MotionEvent &event) + { + actionTime = event.actionTime; + for (auto &it : event.pointers) { + pointers[it.first] = it.second; + } + } + + void initializeFrom(const History &other) + { + actionTime = other.actionTime; + for (auto &it : other.pointers) { + pointers[it.first] = it.second; + } + } + + const Pointer& getPointerById(uint32_t id) const + { + auto item = pointers.find(id); + return item->second; + } + + bool hasPointerId(uint32_t id) const + { + auto item = pointers.find(id); + if (item != pointers.end()) { + return true; + } else { + return false; + } + } + }; + + struct TouchState { + int32_t deviceId; + int32_t source; + size_t historyCurrent; + size_t historySize; + History history[2]; + History lastResample; + + void initialize(int32_t deviceId, int32_t source) + { + this->deviceId = deviceId; + this->source = source; + historyCurrent = 0; + historySize = 0; + lastResample.actionTime = 0; + } + + void addHistory(const MotionEvent &event) + { + historyCurrent ^= 1; + if (historySize < MIN_HISTORY_SIZE) { + historySize += 1; + } + history[historyCurrent].initializeFrom(event); + } + + const History* getHistory(size_t idx) const + { + return &history[(historyCurrent + idx) & 1]; + } + + bool recentCoordinatesAreIdentical(uint32_t id) const + { + // Return true if the two most recently received "raw" coordinates are identical + if (historySize < MIN_HISTORY_SIZE) { + return false; + } + if (!getHistory(0)->hasPointerId(id) || !getHistory(1)->hasPointerId(id)) { + return false; + } + float currentX = getHistory(0)->getPointerById(id).coordX; + float currentY = getHistory(0)->getPointerById(id).coordY; + float previousX = getHistory(1)->getPointerById(id).coordX; + float previousY = getHistory(1)->getPointerById(id).coordY; + if (currentX == previousX && currentY == previousY) { + return true; + } + return false; + } + }; + std::vector touchStates_; + + MotionEvent inputEvent_; + MotionEvent outputEvent_; + MotionEvent deferredEvent_; + int64_t frameTime_ {-1}; + bool msgDeferred_ {false}; + bool resampleTouch_ {true}; + std::shared_ptr pointerEvent_ {nullptr}; + + void UpdatePointerEvent(MotionEvent* outEvent); + ErrCode ConsumeBatch(int64_t frameTime, MotionEvent** outEvent); + ErrCode ConsumeSamples(Batch& batch, size_t count, MotionEvent** outEvent); + void AddSample(MotionEvent* outEvent, const MotionEvent* event); + void UpdateTouchState(MotionEvent &event); + void ResampleTouchState(int64_t sampleTime, MotionEvent* event, const MotionEvent* next); + ssize_t FindBatch(int32_t deviceId, int32_t source) const; + ssize_t FindTouchState(int32_t deviceId, int32_t source) const; + bool CanAddSample(const Batch &batch, MotionEvent &event); + void RewriteMessage(TouchState& state, MotionEvent &event); + ssize_t FindSampleNoLaterThan(const Batch& batch, int64_t time); + bool ShouldResampleTool(int32_t toolType); +}; + +inline static float calcCoord(float a, float b, float alpha) +{ + return a + alpha * (b - a); +} + +#define EVENT_RESAMPLE_HDR ::OHOS::DelayedSingleton::GetInstance() +} // namespace MMI +} // namespace OHOS +#endif // EVENT_RESAMPLE_H diff --git a/service/event_resample/src/event_resample.cpp b/service/event_resample/src/event_resample.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d76dc556c2a4e25201072e21e15db2f4985bffc1 --- /dev/null +++ b/service/event_resample/src/event_resample.cpp @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2021-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. + */ + +#include "event_resample.h" + +#include "event_log_helper.h" +#include "mmi_log.h" +#include "util.h" + +namespace OHOS { +namespace MMI { +namespace { +constexpr OHOS::HiviewDFX::HiLogLabel LABEL { LOG_CORE, MMI_LOG_DOMAIN, "EventResample" }; +} // namespace + +EventResample::EventResample(){}; +EventResample::~EventResample(){}; + +std::shared_ptr EventResample::onEventConsume(std::shared_ptr pointerEvent, + int64_t frameTime, bool &deferred, ErrCode &status) +{ + int32_t pointerAction = PointerEvent::POINTER_ACTION_UNKNOWN; + MotionEvent* outEvent = nullptr; + ErrCode result = ERR_OK; + + if (pointerEvent != nullptr) { + pointerEvent_ = pointerEvent; + } + deferred = false; + status = ERR_OK; + + if (frameTime_ <= 0) { + if (0 != frameTime) { + frameTime_ = frameTime; + } else if (nullptr != pointerEvent) { + frameTime_ = GetSysClockTime(); + } else { + frameTime_ = 0; + } + } + + // Check that event can be consumed and initialize motion event. + if (nullptr != pointerEvent) { + pointerAction = pointerEvent->GetPointerAction(); + MMI_HILOGD("pointerAction:%{public}d %{public}" PRId64 "%{public}" PRId64, pointerAction, + pointerEvent->GetActionTime(), frameTime); + switch (pointerAction) { + case PointerEvent::POINTER_ACTION_DOWN: + case PointerEvent::POINTER_ACTION_MOVE: + case PointerEvent::POINTER_ACTION_UP: + case PointerEvent::POINTER_ACTION_CANCEL: + break; + default: + status = ERR_WOULD_BLOCK; + return pointerEvent; + } + inputEvent_.reset(); + inputEvent_.initializeFrom(pointerEvent); + + for (auto &it : inputEvent_.pointers) { + MMI_HILOGD("Input event: %{public}d %{public}d %{public}" PRId64 " %{public}" PRId64, + it.second.coordX, it.second.coordY, inputEvent_.actionTime, frameTime_); + } + } else { + inputEvent_.reset(); + } + + do { + // All events are dispathed so consume batches + if (PointerEvent::POINTER_ACTION_UNKNOWN == inputEvent_.pointerAction) { + if (msgDeferred_ == true) { + msgDeferred_ = false; + deferred = false; + frameTime_ = 0; + outEvent = &deferredEvent_; + result = ERR_OK; + break; + } + result = ConsumeBatch(frameTime_, &outEvent); + frameTime_ = 0; + if ((ERR_OK == result) && (NULL != outEvent)) { + status = result; + break; + } else { + status = result; + return nullptr; + } + } + + // Add event into batch + ssize_t batchIndex = FindBatch(inputEvent_.deviceId, inputEvent_.sourceType); + if (batchIndex >= 0) { + Batch& batch = batches_.at(batchIndex); + if (CanAddSample(batch, inputEvent_)) { + batch.samples.push_back(inputEvent_); + MMI_HILOGD("Event added to batch: %{public}d %{public}d %{public}d", + inputEvent_.deviceId, inputEvent_.sourceType, inputEvent_.pointerAction); + break; + } else if (PointerEvent::POINTER_ACTION_UP == inputEvent_.pointerAction) { + MMI_HILOGD("Deferred event: %{public}d %{public}d %{public}d", inputEvent_.deviceId, + inputEvent_.sourceType, inputEvent_.pointerAction); + deferredEvent_.initializeFrom(inputEvent_); + msgDeferred_ = true; + deferred = true; + result = ConsumeSamples(batch, batch.samples.size(), &outEvent); + batches_.erase(batches_.begin() + batchIndex); + UpdateTouchState(deferredEvent_); + break; + } + } + + // Start a new batch + if (PointerEvent::POINTER_ACTION_MOVE == inputEvent_.pointerAction) { + Batch batch; + batch.samples.push_back(inputEvent_); + batches_.push_back(std::move(batch)); + break; + } + + // Update touch state object + MMI_HILOGD("UpdateTouchState"); + UpdateTouchState(inputEvent_); + outEvent = &inputEvent_; + } while (0); + + if ((ERR_OK == result) && (NULL != outEvent)) { + // Update pointer event + UpdatePointerEvent(outEvent); + return pointerEvent_; + } + + return nullptr; +} + +std::shared_ptr EventResample::getPointerEvent() +{ + return pointerEvent_; +} + +void EventResample::UpdatePointerEvent(MotionEvent* outEvent) +{ + pointerEvent_->SetActionTime(outEvent->actionTime); + pointerEvent_->SetPointerAction(outEvent->pointerAction); + pointerEvent_->SetActionTime(outEvent->actionTime); + for (auto &it : outEvent->pointers) { + MMI_HILOGD("Output event: %{public}d %{public}d %{public}" PRId64, + it.second.coordX, it.second.coordY, outEvent->actionTime); + PointerEvent::PointerItem item; + if (pointerEvent_->GetPointerItem(it.first, item)) { + item.SetDisplayX(it.second.coordX); + item.SetDisplayY(it.second.coordY); + pointerEvent_->UpdatePointerItem(it.first, item); + } + } +} + +ErrCode EventResample::ConsumeBatch(int64_t frameTime, MotionEvent** outEvent) +{ + int32_t result; + for (size_t i = batches_.size(); i > 0;) { + i--; + Batch& batch = batches_.at(i); + if (frameTime < 0) { + result = ConsumeSamples(batch, batch.samples.size(), outEvent); + batches_.erase(batches_.begin() + i); + return result; + } + + int64_t sampleTime = frameTime; + if (resampleTouch_) { + sampleTime -= RESAMPLE_LATENCY; + } + ssize_t split = FindSampleNoLaterThan(batch, sampleTime); + if (split < 0) { + continue; + } + + result = ConsumeSamples(batch, split + 1, outEvent); + const MotionEvent* next; + if (batch.samples.empty()) { + batches_.erase(batches_.begin() + i); + next = NULL; + } else { + next = &batch.samples.at(0); + } + if (!result && resampleTouch_) { + ResampleTouchState(sampleTime, static_cast(*outEvent), next); + } + return result; + } + + return ERR_WOULD_BLOCK; +} + +ErrCode EventResample::ConsumeSamples(Batch& batch, size_t count, MotionEvent** outEvent) +{ + outputEvent_.reset(); + + for (size_t i = 0; i < count; i++) { + MotionEvent& event = batch.samples.at(i); + UpdateTouchState(event); + if (i > 0) { + AddSample(&outputEvent_, &event); + } else { + outputEvent_.initializeFrom(event); + } + } + batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count); + + *outEvent = &outputEvent_; + + return ERR_OK; +} + +void EventResample::AddSample(MotionEvent* outEvent, const MotionEvent* event) +{ + outEvent->actionTime = event->actionTime; + for (auto &it : event->pointers) { + outEvent->pointers[it.first] = it.second; + } +} + +void EventResample::UpdateTouchState(MotionEvent &event) +{ + int32_t deviceId = event.deviceId; + int32_t source = event.sourceType; + + switch (event.pointerAction) { + case PointerEvent::POINTER_ACTION_DOWN: { + ssize_t idx = FindTouchState(deviceId, source); + if (idx < 0) { + TouchState newState; + touchStates_.push_back(newState); + idx = touchStates_.size() - 1; + } + TouchState& touchState = touchStates_.at(idx); + touchState.initialize(deviceId, source); + touchState.addHistory(event); + break; + } + case PointerEvent::POINTER_ACTION_MOVE: { + ssize_t idx = FindTouchState(deviceId, source); + if (idx >= 0) { + TouchState& touchState = touchStates_.at(idx); + touchState.addHistory(event); + RewriteMessage(touchState, event); + } + break; + } + case PointerEvent::POINTER_ACTION_UP: + case PointerEvent::POINTER_ACTION_CANCEL: { + ssize_t idx = FindTouchState(deviceId, source); + if (idx >= 0) { + TouchState& touchState = touchStates_.at(idx); + RewriteMessage(touchState, event); + touchStates_.erase(touchStates_.begin() + idx); + } + frameTime_ = 0; + idx = FindBatch(deviceId, source); + if (idx >= 0) { + batches_.erase(batches_.begin() + idx); + } + break; + } + default: { + break; + } + } +} + +void EventResample::ResampleTouchState(int64_t sampleTime, MotionEvent* event, const MotionEvent* next) +{ + if (!resampleTouch_ || (PointerEvent::SOURCE_TYPE_TOUCHSCREEN != event->sourceType) + || (PointerEvent::POINTER_ACTION_MOVE != event->pointerAction)) { + return; + } + + ssize_t idx = FindTouchState(event->deviceId, event->sourceType); + if (idx < 0) { + return; + } + + TouchState& touchState = touchStates_.at(idx); + if (touchState.historySize < 1) { + return; + } + + // Ensure that the current sample has all of the pointers that need to be reported. + const History* current = touchState.getHistory(0); + for (auto &it : event->pointers) { + if (!current->hasPointerId(it.first)) { + return; + } + } + + // Find the data to use for resampling. + const History* other; + History future; + float alpha; + if (next) { + // Interpolate between current sample and future sample. + // So current->actionTime <= sampleTime <= future.actionTime. + future.initializeFrom(*next); + other = &future; + int64_t delta = future.actionTime - current->actionTime; + if (delta < RESAMPLE_MIN_DELTA) { + return; + } + alpha = static_cast(sampleTime - current->actionTime) / delta; + } else if (touchState.historySize >= MIN_HISTORY_SIZE) { + // Extrapolate future sample using current sample and past sample. + // So other->actionTime <= current->actionTime <= sampleTime. + other = touchState.getHistory(1); + int64_t delta = current->actionTime - other->actionTime; + if (delta < RESAMPLE_MIN_DELTA) { + return; + } else if (delta > RESAMPLE_MAX_DELTA) { + return; + } + int64_t maxPredict = current->actionTime + std::min(delta / 2, RESAMPLE_MAX_PREDICTION); + if (sampleTime > maxPredict) { + sampleTime = maxPredict; + } + alpha = static_cast(current->actionTime - sampleTime) / delta; + } else { + return; + } + + // Resample touch coordinates. + History oldLastResample; + oldLastResample.initializeFrom(touchState.lastResample); + touchState.lastResample.actionTime = sampleTime; + + for (auto &it : event->pointers) { + uint32_t id = it.first; + if (oldLastResample.hasPointerId(id) && touchState.recentCoordinatesAreIdentical(id)) { + auto lastItem = touchState.lastResample.pointers.find(id); + if (lastItem != touchState.lastResample.pointers.end()) { + auto oldLastItem = oldLastResample.pointers.find(id); + lastItem->second.copyFrom(oldLastItem->second); + } + continue; + } + + Pointer resampledCoords; + const Pointer& currentCoords = current->getPointerById(id); + resampledCoords.copyFrom(currentCoords); + auto item = event->pointers.find(id); + if (item == event->pointers.end()) { + return; + } + if (other->hasPointerId(id) && ShouldResampleTool(item->second.toolType)) { + const Pointer& otherCoords = other->getPointerById(id); + resampledCoords.coordX = calcCoord(currentCoords.coordX, otherCoords.coordX, alpha); + resampledCoords.coordY = calcCoord(currentCoords.coordY, otherCoords.coordY, alpha); + } + item->second.copyFrom(resampledCoords); + event->actionTime = sampleTime; + } +} + +ssize_t EventResample::FindBatch(int32_t deviceId, int32_t source) const +{ + ssize_t idx = 0; + for (auto it = batches_.begin(); it < batches_.end(); ++it, ++idx) { + const MotionEvent& head = it->samples.at(0); + if ((head.deviceId == deviceId) && (head.sourceType == source)) { + return idx; + } + } + return -1; +} + +ssize_t EventResample::FindTouchState(int32_t deviceId, int32_t source) const +{ + ssize_t idx = 0; + for (auto it = touchStates_.begin(); it < touchStates_.end(); ++it, ++idx) { + if ((it->deviceId == deviceId) && (it->source == source)) { + return idx; + } + } + return -1; +} + +bool EventResample::CanAddSample(const Batch &batch, MotionEvent &event) +{ + const MotionEvent& head = batch.samples.at(0); + uint32_t pointerCount = event.pointerCount; + int32_t pointerAction = event.pointerAction; + if ((head.pointerCount != pointerCount) || (head.pointerAction != pointerAction)) { + return false; + } + + return true; +} + +void EventResample::RewriteMessage(TouchState& state, MotionEvent &event) +{ + for (auto &it : event.pointers) { + uint32_t id = it.first; + if (state.lastResample.hasPointerId(id)) { + if ((event.actionTime < state.lastResample.actionTime) || state.recentCoordinatesAreIdentical(id)) { + Pointer& msgCoords = it.second; + const Pointer& resampleCoords = state.lastResample.getPointerById(id); + msgCoords.copyFrom(resampleCoords); + } else { + state.lastResample.pointers.erase(id); + } + } + } +} + +ssize_t EventResample::FindSampleNoLaterThan(const Batch& batch, int64_t time) +{ + size_t numSamples = batch.samples.size(); + size_t idx = 0; + while ((idx < numSamples) && (batch.samples.at(idx).actionTime <= time)) { + idx += 1; + } + return ssize_t(idx) - 1; +} + +bool EventResample::ShouldResampleTool(int32_t toolType) +{ + switch (toolType) { + case PointerEvent::TOOL_TYPE_FINGER: + case PointerEvent::TOOL_TYPE_PEN: + return true; + default: + return false; + } +} + +} // namespace MMI +} // namespace OHOS diff --git a/service/event_resample/test/event_resample_test.cpp b/service/event_resample/test/event_resample_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a9e6f2c14b19e3c7650626067ece0938314af7c0 --- /dev/null +++ b/service/event_resample/test/event_resample_test.cpp @@ -0,0 +1,568 @@ +/* + * 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. + */ + +#include + +#include +#include + +#include "event_resample.h" +#include "mmi_log.h" + +namespace OHOS { +namespace MMI { + +namespace { +using namespace testing::ext; +constexpr OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, MMI_LOG_DOMAIN, "EventResampleTest" }; +constexpr int64_t START_TIME = 10000; +constexpr int64_t TIME_DELTA = 2500; +constexpr uint32_t INITIAL_COORDS = 10; +constexpr uint32_t COORDS_DELTA = 10; +constexpr uint32_t MIN_TOUCH_STATES = 2; +constexpr int64_t FRAME_TIME = 8000; +} // namespace + +class EventResampleTest : public testing::Test { +public: + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) {} + + struct TestData { + uint32_t framesNum { 10 }; + uint32_t pointerId { 0 }; + uint32_t fingerNum { 1 }; + uint32_t evtNum { 0 }; + int64_t timeDelta { TIME_DELTA }; + uint32_t coordsDelta { COORDS_DELTA }; + }; + + struct Context { + int32_t lastDispX { INITIAL_COORDS }; + int32_t lastDispY { INITIAL_COORDS }; + int64_t lastTime { START_TIME }; + int64_t frameTime { START_TIME }; + int64_t lastFrameTime { START_TIME }; + + void Reset(void) + { + lastDispX = INITIAL_COORDS; + lastDispY = INITIAL_COORDS; + lastTime = START_TIME; + frameTime = START_TIME; + lastFrameTime = START_TIME; + } + }; + + struct InputEvt { + int32_t action { PointerEvent::POINTER_ACTION_UNKNOWN }; + int64_t actionTime { START_TIME }; + int32_t dispX { INITIAL_COORDS }; + int32_t dispY { INITIAL_COORDS }; + int32_t id { 0 }; + + void initialize(int32_t action, TestData &testData, Context &context) + { + this->action = action; + dispX = context.lastDispX + testData.coordsDelta; + context.lastDispX = dispX; + dispY = context.lastDispY + testData.coordsDelta; + context.lastDispY = dispY; + id = testData.pointerId; + + if (action == PointerEvent::POINTER_ACTION_DOWN) { + actionTime = context.lastFrameTime; + context.lastTime = 0; + } else { + actionTime = context.lastFrameTime + context.lastTime + testData.timeDelta; + context.lastTime = actionTime - context.lastFrameTime; + } + } + + void InitializeFrom(InputEvt &event) + { + action = event.action; + actionTime = event.actionTime; + dispX = event.dispX; + dispY = event.dispY; + id = event.id; + }; + }; + + struct ExpectedData { + int64_t actionTime { 0 }; + int32_t dispX { 0 }; + int32_t dispY { 0 }; + int64_t touchUpTime { 0 }; + int32_t touchUpX { 0 }; + int32_t touchUpY { 0 }; + int32_t id { 0 }; + bool deferred { false }; + + ExpectedData() {} + explicit ExpectedData(int32_t id) : id(id) {} + + void reset(int32_t id) + { + this->id = id; + actionTime = 0; + dispX = 0; + dispY = 0; + touchUpTime = 0; + touchUpX = 0; + touchUpY = 0; + deferred = false; + } + + void UpdateTouchState(InputEvt &event) + { + if (id != event.id) { + return; + } + + switch (event.action) { + case PointerEvent::POINTER_ACTION_DOWN : { + touchState.clear(); + eventBatch.clear(); + InputEvt evt; + evt.InitializeFrom(event); + touchState.insert(touchState.begin(), std::move(evt)); + actionTime = event.actionTime; + dispX = event.dispX; + dispY = event.dispY; + break; + } + case PointerEvent::POINTER_ACTION_UP : { + touchState.clear(); + eventBatch.clear(); + touchUpTime = event.actionTime; + touchUpX = event.dispX; + touchUpY = event.dispY; + break; + } + case PointerEvent::POINTER_ACTION_MOVE : { + while (touchState.size() > 1) { + touchState.pop_back(); + } + InputEvt evt; + evt.InitializeFrom(event); + touchState.insert(touchState.begin(), std::move(evt)); + actionTime = event.actionTime; + dispX = event.dispX; + dispY = event.dispY; + break; + } + } + } + + void AddEvent(InputEvt &event) + { + if (id != event.id) { + return; + } + + if (event.action == PointerEvent::POINTER_ACTION_MOVE) { + InputEvt evt; + evt.InitializeFrom(event); + eventBatch.push_back(std::move(evt)); + } else if (event.action == PointerEvent::POINTER_ACTION_UP && !eventBatch.empty()) { + for (size_t i = 0; i < eventBatch.size(); i++) { + InputEvt& event = eventBatch.at(i); + UpdateTouchState(event); + } + eventBatch.erase(eventBatch.begin(), eventBatch.begin() + eventBatch.size() - 1); + deferred = true; + } else { + UpdateTouchState(event); + } + } + + int32_t calculateExpected(int64_t frameTime) + { + float alpha = 0.0; + int64_t sampleTime = frameTime - EventResample::RESAMPLE_LATENCY; + InputEvt current; + InputEvt other; + + if (eventBatch.empty()) { + MMI_HILOGD("Event Batch empty"); + return ERR_WOULD_BLOCK; + } + + size_t numSamples = eventBatch.size(); + size_t idx = 0; + while ((idx < numSamples) && (eventBatch.at(idx).actionTime <= sampleTime)) { + idx += 1; + } + ssize_t split = ssize_t(idx) - 1; + if (split < 0) { + MMI_HILOGD("Negative split value"); + return ERR_WOULD_BLOCK; + } + size_t count = split + 1; + + // Consume samples in batch + for (size_t i = 0; i < count; i++) { + InputEvt& event = eventBatch.at(i); + UpdateTouchState(event); + } + eventBatch.erase(eventBatch.begin(), eventBatch.begin() + count); + + current.InitializeFrom(touchState[0]); + + if (eventBatch.empty()) { + // Coordinates extrapolation + MMI_HILOGD("Extrapolation"); + if (touchState.size() < MIN_TOUCH_STATES) { + return ERR_OK; + } + other.InitializeFrom(touchState[1]); + int64_t delta = touchState[0].actionTime - touchState[1].actionTime; + if (delta < EventResample::RESAMPLE_MIN_DELTA) { + return ERR_OK; + } else if (delta > EventResample::RESAMPLE_MAX_DELTA) { + return ERR_OK; + } + int64_t maxPredict = touchState[0].actionTime + std::min(delta / 2, + EventResample::RESAMPLE_MAX_PREDICTION); + if (sampleTime > maxPredict) { + sampleTime = maxPredict; + } + alpha = static_cast(touchState[0].actionTime - sampleTime) / delta; + } else { + // Coordinates interpolation + MMI_HILOGD("Interpolation"); + InputEvt &next = eventBatch.front(); + other.InitializeFrom(next); + int64_t delta = next.actionTime - touchState[0].actionTime; + if (delta < EventResample::RESAMPLE_MIN_DELTA) { + MMI_HILOGD("RESAMPLE_MIN_DELTA = %{public}" PRId64 ", next_x = %{public}d, ts0_x = %{public}d", + delta, next.dispX, touchState[0].dispX); + return ERR_OK; + } + alpha = static_cast(sampleTime - touchState[0].actionTime) / delta; + } + + dispX = calcCoord(current.dispX, other.dispX, alpha); + dispY = calcCoord(current.dispY, other.dispY, alpha); + actionTime = sampleTime; + + return ERR_OK; + } + + protected: + std::vector touchState; + std::vector eventBatch; + }; + + EventResampleTest(); + ~EventResampleTest(); + + bool SetupPointerEvent(InputEvt &event, TestData &testData); + int32_t CheckResults(std::shared_ptr outEvent, std::vector &expected, Context &context); + bool DoTest(TestData &testData, int32_t testId); + + std::shared_ptr pointerEvent_ = nullptr; + std::queue eventQueue_; + uint32_t failCount_ = 0; +}; + +EventResampleTest::EventResampleTest() +{ + pointerEvent_ = PointerEvent::Create(); +} + +EventResampleTest::~EventResampleTest() +{ +} + +bool EventResampleTest::SetupPointerEvent(InputEvt &event, TestData &testData) +{ + pointerEvent_->SetSourceType(PointerEvent::SOURCE_TYPE_TOUCHSCREEN); + pointerEvent_->SetPointerAction(event.action); + pointerEvent_->SetPointerId(event.id); + pointerEvent_->SetDeviceId(0); + + auto pointIds = pointerEvent_->GetPointerIds(); + int64_t time = event.actionTime; + if (pointIds.empty()) { + pointerEvent_->SetActionStartTime(time); + } + pointerEvent_->SetActionTime(time); + + for (uint32_t idx = 0; idx < testData.fingerNum; idx++) { + PointerEvent::PointerItem item; + if (pointerEvent_->GetPointerItem(idx, item)) { + item.SetPointerId(idx); + item.SetDisplayX(event.dispX); + item.SetDisplayY(event.dispY); + item.SetToolType(PointerEvent::TOOL_TYPE_FINGER); + item.SetDeviceId(0); + pointerEvent_->UpdatePointerItem(idx, item); + } else { + item.SetPointerId(idx); + item.SetDisplayX(event.dispX); + item.SetDisplayY(event.dispY); + item.SetToolType(PointerEvent::TOOL_TYPE_FINGER); + item.SetDeviceId(0); + pointerEvent_->AddPointerItem(item); + } + } + + return true; +} + +int32_t EventResampleTest::CheckResults(std::shared_ptr outEvent, + std::vector &expected, Context &context) +{ + bool ret = ERR_OK; + int32_t failCount = 0; + int64_t actionTime = 0; + int32_t dispX = 0; + int32_t dispY = 0; + + for (auto &it : expected) { + PointerEvent::PointerItem pointerItem; + if (!outEvent->GetPointerItem(it.id, pointerItem)) { + EXPECT_TRUE(false); + ret = ERR_INVALID_VALUE; + break; + } + + if (outEvent->GetPointerAction() == PointerEvent::POINTER_ACTION_DOWN) { + actionTime = expected[it.id].actionTime; + dispX = expected[it.id].dispX; + dispY = expected[it.id].dispY; + } else if (outEvent->GetPointerAction() == PointerEvent::POINTER_ACTION_MOVE) { + expected[it.id].calculateExpected(context.frameTime); + actionTime = expected[it.id].actionTime; + dispX = expected[it.id].dispX; + dispY = expected[it.id].dispY; + } else if (outEvent->GetPointerAction() == PointerEvent::POINTER_ACTION_UP) { + actionTime = expected[it.id].touchUpTime; + dispX = expected[it.id].touchUpX; + dispY = expected[it.id].touchUpY; + } + + MMI_HILOGD("OutEvent: x = %{public}d y = %{public}d t = %{public}" PRId64 + " f = %{public}" PRId64 " (%{public}d)", + pointerItem.GetDisplayX(), pointerItem.GetDisplayY(), + outEvent->GetActionTime(), context.frameTime, outEvent->GetPointerAction()); + MMI_HILOGD("Expected: x = %{public}d y = %{public}d t = %{public}" PRId64, dispX, dispY, actionTime); + + if (pointerItem.GetDisplayX() != dispX) { + failCount++; + EXPECT_EQ(pointerItem.GetDisplayX(), dispX); + } + if (pointerItem.GetDisplayY() != dispY) { + failCount++; + EXPECT_EQ(pointerItem.GetDisplayY(), dispY); + } + if (outEvent->GetActionTime() != actionTime) { + failCount++; + EXPECT_EQ(outEvent->GetActionTime(), actionTime); + } + + if (failCount != 0) { + MMI_HILOGD("Test Failed ------------------------------------"); + } + failCount_ += failCount; + } + + return ret; +} + +bool EventResampleTest::DoTest(TestData &testData, int32_t testId) +{ + CHKPF(pointerEvent_); + pointerEvent_->Reset(); + Context ctx; + bool deferred = false; + ErrCode status = RET_OK; + std::shared_ptr outEvent = nullptr; + std::vector expected(testData.fingerNum); + + MMI_HILOGD("Start test %{public}d -------------------------------------", testId); + + for (uint32_t idx = 0; idx < testData.fingerNum; idx++) { + expected[idx].reset(idx); + } + + failCount_ = 0; + ctx.Reset(); + + // Send touch down event + InputEvt touchDown; + touchDown.initialize(PointerEvent::POINTER_ACTION_DOWN, testData, ctx); + eventQueue_.push(std::move(touchDown)); + + // Send touch moving events + for (uint32_t idx = 0; idx < testData.framesNum; idx++) { + ctx.lastFrameTime = ctx.frameTime; + ctx.frameTime += FRAME_TIME; + ctx.lastTime = 0; + MMI_HILOGD("Frame %{public}d: lf = %{public}" PRId64 " f = %{public}" PRId64, + idx, ctx.lastFrameTime, ctx.frameTime); + + for (uint32_t eidx = 0; eidx < testData.evtNum; eidx++) { + InputEvt touchMove; + touchMove.initialize(PointerEvent::POINTER_ACTION_MOVE, testData, ctx); + eventQueue_.push(std::move(touchMove)); + } + + while (!eventQueue_.empty()) { + InputEvt &event = eventQueue_.front(); + expected[event.id].AddEvent(event); + SetupPointerEvent(event, testData); + + PointerEvent::PointerItem pointerItem; + pointerEvent_->GetPointerItem(0, pointerItem); + MMI_HILOGD("pointerEvent_: x = %{public}d y = %{public}d t = %{public}" PRId64, + pointerItem.GetDisplayX(), pointerItem.GetDisplayY(), pointerEvent_->GetActionTime()); + + outEvent = EVENT_RESAMPLE_HDR->onEventConsume(pointerEvent_, ctx.frameTime, deferred, status); + if ((outEvent != nullptr) && (PointerEvent::POINTER_ACTION_DOWN != outEvent->GetPointerAction())) { + MMI_HILOGE("Unexpected pointer action: %{public}d while %{public}d expected", + outEvent->GetPointerAction(), PointerEvent::POINTER_ACTION_DOWN); + failCount_++; + } else if (outEvent != nullptr) { + EXPECT_EQ(ERR_OK, CheckResults(outEvent, expected, ctx)); + EXPECT_EQ(ERR_OK, status); + EXPECT_FALSE(deferred); + } + eventQueue_.pop(); + } + + outEvent = EVENT_RESAMPLE_HDR->onEventConsume(nullptr, ctx.frameTime, deferred, status); + if (outEvent != nullptr) { + EXPECT_EQ(ERR_OK, CheckResults(outEvent, expected, ctx)); + EXPECT_EQ(ERR_OK, status); + EXPECT_FALSE(deferred); + } else { + MMI_HILOGD("NULL Event_: status = %{public}d", status); + } + } + + // Send touch up event + InputEvt touchUp; + touchUp.initialize(PointerEvent::POINTER_ACTION_UP, testData, ctx); + expected[touchUp.id].AddEvent(touchUp); + SetupPointerEvent(touchUp, testData); + outEvent = EVENT_RESAMPLE_HDR->onEventConsume(pointerEvent_, ctx.frameTime, deferred, status); + if (outEvent != nullptr) { + MMI_HILOGD("Pointer Action: %{public}d", outEvent->GetPointerAction()); + EXPECT_EQ(ERR_OK, CheckResults(outEvent, expected, ctx)); + EXPECT_EQ(ERR_OK, status); + } else { + MMI_HILOGD("NULL Event_: status = %{public}d", status); + } + + if (deferred) { + outEvent = EVENT_RESAMPLE_HDR->onEventConsume(nullptr, ctx.frameTime, deferred, status); + if (outEvent != nullptr) { + MMI_HILOGD("Deferred Event: %{public}d", outEvent->GetPointerAction()); + EXPECT_EQ(ERR_OK, CheckResults(outEvent, expected, ctx)); + EXPECT_EQ(ERR_OK, status); + } else { + MMI_HILOGE("Deferred event not received"); + failCount_++; + } + } + + return (failCount_ != 0) ? false : true; +} + +/** + * @tc.name: EventResampleTest_001 + * @tc.desc: Test to check single touch without moving events + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventResampleTest, EventResampleTest_001, TestSize.Level1) +{ + CALL_TEST_DEBUG; + TestData testData = {.framesNum = 5, .fingerNum = 1, .evtNum = 0}; + EXPECT_EQ(EVENT_RESAMPLE_HDR->getPointerEvent(), nullptr); + EXPECT_TRUE(DoTest(testData, 1)); + EXPECT_NE(EVENT_RESAMPLE_HDR->getPointerEvent(), nullptr); +} + +/** + * @tc.name: EventResampleTest_002 + * @tc.desc: Basic test to check events interpolation + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventResampleTest, EventResampleTest_002, TestSize.Level1) +{ + CALL_TEST_DEBUG; + TestData testData = {.framesNum = 5, .fingerNum = 1, .evtNum = 2}; + EXPECT_TRUE(DoTest(testData, 2)); +} + +/** + * @tc.name: EventResampleTest_003 + * @tc.desc: Basic test to check events extrapolation + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventResampleTest, EventResampleTest_003, TestSize.Level1) +{ + CALL_TEST_DEBUG; + TestData testData = {.framesNum = 5, .fingerNum = 1, .evtNum = 1}; + EXPECT_TRUE(DoTest(testData, 3)); +} + +/** + * @tc.name: EventResampleTest_004 + * @tc.desc: Test to check interpolation behavior when event received later then latency time + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventResampleTest, EventResampleTest_004, TestSize.Level1) +{ + CALL_TEST_DEBUG; + TestData testData = {.framesNum = 5, .fingerNum = 1, .evtNum = 1, .timeDelta = 6000}; + EXPECT_TRUE(DoTest(testData, 4)); +} + +/** + * @tc.name: EventResampleTest_005 + * @tc.desc: Test to check case when events intervals less than minimal delta value + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventResampleTest, EventResampleTest_005, TestSize.Level1) +{ + CALL_TEST_DEBUG; + TestData testData = {.framesNum = 5, .fingerNum = 1, .evtNum = 5, .timeDelta = 1000}; + EXPECT_TRUE(DoTest(testData, 5)); +} + +/** + * @tc.name: EventResampleTest_006 + * @tc.desc: Test to check case when many events are received during time frame + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventResampleTest, EventResampleTest_006, TestSize.Level1) +{ + CALL_TEST_DEBUG; + TestData testData = {.framesNum = 5, .fingerNum = 1, .evtNum = 3}; + EXPECT_TRUE(DoTest(testData, 6)); +} + +} // namespace MMI +} // namespace OHOS diff --git a/service/key_command/include/key_command_handler.h b/service/key_command/include/key_command_handler.h index cae9730592efbc9fb44f15a8c16b5daae6d2cc19..58a7465ff12026a56b2126eb62167d58dfd38fed 100755 --- a/service/key_command/include/key_command_handler.h +++ b/service/key_command/include/key_command_handler.h @@ -123,7 +123,12 @@ public: void HandlePointerActionUpEvent(const std::shared_ptr touchEvent); #endif // OHOS_BUILD_ENABLE_TOUCH bool OnHandleEvent(const std::shared_ptr keyEvent); + +#ifdef UNIT_TEST +public: +#else private: +#endif void Print(); void PrintSeq(); bool ParseConfig(); diff --git a/service/key_command/src/key_command_handler.cpp b/service/key_command/src/key_command_handler.cpp index 1f4ccfaea65acadd661ae2f5e7b75c6c8e4163ee..746e632b1ee725b847258c5bdd504d418ca5b1bc 100644 --- a/service/key_command/src/key_command_handler.cpp +++ b/service/key_command/src/key_command_handler.cpp @@ -974,10 +974,18 @@ void KeyCommandHandler::StopTwoFingerGesture() bool KeyCommandHandler::ParseConfig() { +#ifndef UNIT_TEST const char *testPathSuffix = "/etc/multimodalinput/ability_launch_config.json"; +#else + const char *testPathSuffix = "/data/test/test.json"; +#endif char buf[MAX_PATH_LEN] = { 0 }; char *filePath = GetOneCfgFile(testPathSuffix, buf, MAX_PATH_LEN); +#ifndef UNIT_TEST std::string defaultConfig = "/system/etc/multimodalinput/ability_launch_config.json"; +#else + std::string defaultConfig = "/data/test/test.json"; +#endif if (filePath == nullptr || filePath[0] == '\0' || strlen(filePath) > MAX_PATH_LEN) { MMI_HILOGD("Can not get customization config file"); return ParseJson(defaultConfig); @@ -1424,7 +1432,7 @@ void KeyCommandHandler::LaunchAbility(const Ability &ability, int64_t delay) want.SetElementName(ability.deviceId, ability.bundleName, ability.abilityName); want.SetAction(ability.action); want.SetUri(ability.uri); - want.SetType(ability.uri); + want.SetType(ability.type); for (const auto &entity : ability.entities) { want.AddEntity(entity); } diff --git a/service/key_command/test/ability_launch_config.json b/service/key_command/test/ability_launch_config.json new file mode 100644 index 0000000000000000000000000000000000000000..a2ea869eea82364336870665290580f7dcee9e93 --- /dev/null +++ b/service/key_command/test/ability_launch_config.json @@ -0,0 +1,31 @@ +{ + "Shortkeys": [ + ], + "Sequences" : [ + ], + "TwoFingerGesture" : { + "abilityStartDelay" : 20, + "ability" : { + "bundleName" : "bindle_name", + "abilityName" : "test_ability", + "action" : "some_action", + "type" : "some_type", + "deviceId" : "device_id", + "uri" : "uri", + "entities" : [ + "entity1", + "entity2" + ], + "params" : [ + { + "key" : "key1", + "value" : "value1" + }, + { + "key" : "key2", + "value" : "value2" + } + ] + } + } +} diff --git a/service/key_command/test/ability_manager_client.h b/service/key_command/test/ability_manager_client.h new file mode 100644 index 0000000000000000000000000000000000000000..a18505272e671098206da8b26f837392568f27d7 --- /dev/null +++ b/service/key_command/test/ability_manager_client.h @@ -0,0 +1,80 @@ +/* + * 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. + */ + +/* + * This file is used as stub header for key_command_handler.cpp. + * It should re-define ability related methods for unit test. + */ + +#ifndef ABILITY_MANAGER_CLIENT_STUB_H +#define ABILITY_MANAGER_CLIENT_STUB_H + +#include +#include +#include +#include +#include + +namespace OHOS { +namespace AAFwk { + +constexpr int32_t DEFAULT_INVAL_VALUE = -1; + +class Want { +public: + Want &SetElementName(const std::string &deviceId, const std::string &bundleName, + const std::string &abilityName, const std::string &moduleName = ""); + Want &SetAction(const std::string &action); + Want &SetUri(const std::string &uri); + Want &SetType(const std::string &type); + Want &AddEntity(const std::string &entity); + // Note: We use different SetParam() signature for test + Want &SetParam(const std::string& key, const std::string& value); + + std::string bundleName_; + std::string abilityName_; + std::string action_; + std::string type_; + std::string deviceId_; + std::string uri_; + std::string moduleName_; + std::vector entities_; + std::map params_; +}; + +class AbilityManagerClient { +public: + AbilityManagerClient() + { + callback_ = nullptr; + err_ = ERR_OK; + } + virtual ~AbilityManagerClient() {} + static std::shared_ptr GetInstance(); + ErrCode StartAbility(const Want &want, int32_t requestCode = DEFAULT_INVAL_VALUE, + int32_t userId = DEFAULT_INVAL_VALUE); + void SetCallback(void (*cb)(const Want &want, ErrCode err)); + void SetErrCode(ErrCode err); + +private: + static std::shared_ptr instance_; + void (*callback_)(const Want &want, ErrCode err); + ErrCode err_; +}; + +} // namespace AAFwk +} // namespace OHOS + +#endif // ABILITY_MANAGER_CLIENT_STUB_H diff --git a/service/key_command/test/ability_manager_client_stub.cpp b/service/key_command/test/ability_manager_client_stub.cpp new file mode 100644 index 0000000000000000000000000000000000000000..32b2ec8121b1c753c5480891535f04e71987f5f9 --- /dev/null +++ b/service/key_command/test/ability_manager_client_stub.cpp @@ -0,0 +1,105 @@ +/* + * 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. + */ + +#include "ability_manager_client.h" +#include "mmi_log.h" + +namespace OHOS { +namespace AAFwk { + +constexpr OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, MMI::MMI_LOG_DOMAIN, "AbilityManagerClientStub" }; + +std::shared_ptr AbilityManagerClient::instance_ = nullptr; + +Want &Want::SetElementName(const std::string &deviceId, const std::string &bundleName, + const std::string &abilityName, const std::string &moduleName) +{ + deviceId_ = deviceId; + bundleName_ = bundleName; + abilityName_ = abilityName; + moduleName_ = moduleName; + + return *this; +} + +Want &Want::SetAction(const std::string &action) +{ + action_ = action; + + return *this; +} + +Want &Want::SetUri(const std::string &uri) +{ + uri_ = uri; + + return *this; +} + +Want &Want::SetType(const std::string &type) +{ + type_ = type; + + return *this; +} + +Want &Want::AddEntity(const std::string &entity) +{ + entities_.push_back(entity); + + return *this; +} + +Want &Want::SetParam(const std::string& key, const std::string& value) +{ + params_.emplace(key, value); + + return *this; +} + +std::shared_ptr AbilityManagerClient::GetInstance() +{ + if (instance_ == nullptr) { + instance_ = std::make_shared (); + } + return instance_; +} + +ErrCode AbilityManagerClient::StartAbility(const Want &want, int32_t requestCode, int32_t userId) +{ + (void)want; + (void)requestCode; + (void)userId; + + MMI_HILOGI("StartAbility called"); + if (callback_ != nullptr) { + callback_(want, err_); + } + + return err_; +} + +void AbilityManagerClient::SetCallback(void (*cb)(const Want &want, ErrCode err)) +{ + callback_ = cb; +} + +void AbilityManagerClient::SetErrCode(ErrCode err) +{ + err_ = err; +} + +} // namespace AAFwk +} // namespace OHOS diff --git a/service/key_command/test/two_finger_long_touch_test.cpp b/service/key_command/test/two_finger_long_touch_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..21e4255a33818bac03fc257748526bf743a680de --- /dev/null +++ b/service/key_command/test/two_finger_long_touch_test.cpp @@ -0,0 +1,503 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +#include "key_command_handler.h" +#include "input_event_handler.h" +#include "mmi_log.h" +#include "timer_manager.h" + +// Ability manager stub header +#include "ability_manager_client.h" + +namespace OHOS { +namespace MMI { + +namespace { +using namespace testing::ext; +constexpr OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, MMI_LOG_DOMAIN, "TwoFingerLongTouchTest" }; +constexpr std::chrono::milliseconds WAIT_TIME_MS(50); + +const std::string TEST_DIR = "/data/test"; +const std::string TEST_JSON = "/data/test/test.json"; + +const std::string BINDLE_NAME = "bindle_name"; +const std::string ABILITY_NAME = "test_ability"; +const std::string ACTION = "some_action"; +const std::string TYPE = "some_type"; +const std::string DEVICE_ID = "device_id"; +const std::string URI = "uri"; +const std::string ENTITY = "entity"; +const std::string KEY = "key"; +const std::string VALUE = "value"; + +constexpr unsigned ENTITY_NUM = 2; +constexpr unsigned PARAMETERS_NUM = 2; +constexpr int32_t DEFX = 50; +constexpr int32_t DEFY = 50; +constexpr int32_t DISP_INITIAL_POS_X = 25; +constexpr int32_t DISP_INITIAL_POS_Y = 25; +constexpr int32_t LESS_THEN_THRESHOLD = 10; +constexpr int32_t TWO_FINGERS = 2; +constexpr int32_t GREATER_THEN_THRESHOLD = 20; +} // namespace + +class TestCommandHandler final : public IInputEventHandler { +#ifdef OHOS_BUILD_ENABLE_KEYBOARD + virtual void HandleKeyEvent(const std::shared_ptr keyEvent) {} +#endif // OHOS_BUILD_ENABLE_KEYBOARD +#ifdef OHOS_BUILD_ENABLE_POINTER + virtual void HandlePointerEvent(const std::shared_ptr pointerEvent) {} +#endif // OHOS_BUILD_ENABLE_POINTER +#ifdef OHOS_BUILD_ENABLE_TOUCH + virtual void HandleTouchEvent(const std::shared_ptr pointerEvent) {} +#endif // OHOS_BUILD_ENABLE_TOUCH +}; + +const std::string ABILITY_CONFIG_JSON = \ +"{\n" \ +" \"Shortkeys\": [\n" \ +" ],\n" \ +" \"Sequences\" : [\n" \ +" ],\n" \ +" \"TwoFingerGesture\" : {\n" \ +" \"abilityStartDelay\" : 20,\n" \ +" \"ability\" : {\n" \ +" \"bundleName\" : \"bindle_name\",\n" \ +" \"abilityName\" : \"test_ability\",\n" \ +" \"action\" : \"some_action\",\n" \ +" \"type\" : \"some_type\",\n" \ +" \"deviceId\" : \"device_id\",\n" \ +" \"uri\" : \"uri\",\n" \ +" \"entities\" : [\n" \ +" \"entity1\",\n" \ +" \"entity2\"\n" \ +" ],\n" \ +" \"params\" : [\n" \ +" {\n" \ +" \"key\" : \"key1\",\n" \ +" \"value\" : \"value1\"\n" \ +" },\n" \ +" {\n" \ +" \"key\" : \"key2\",\n" \ +" \"value\" : \"value2\"\n" \ +" }\n" \ +" ]\n" \ +" }\n" \ +" }\n" \ +"}\n"; + +class TwoFingerLongTouchTest : public testing::Test { +public: + std::shared_ptr eventTestCommandHandler_ { nullptr }; + std::shared_ptr eventKeyCommandHandler_ { nullptr }; + void SetupKeyCommandHandler(); + std::shared_ptr SetupPointerEvent(int32_t action, int32_t pointerId, int32_t finger_num, + int32_t dispX = DEFX, int32_t dispY = DEFY); + bool CreateTestJson(const std::string &contentJson); + void Delay(std::chrono::milliseconds delayMs); + static void AbilityCallback(const AAFwk::Want &want, ErrCode err); + + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) {} + + TwoFingerLongTouchTest() + { + SetupKeyCommandHandler(); + CreateTestJson(ABILITY_CONFIG_JSON); + } + + ~TwoFingerLongTouchTest() {} + + static inline bool abilityStarted_; + static inline ErrCode err_; +}; + +void TwoFingerLongTouchTest::SetupKeyCommandHandler() +{ + eventTestCommandHandler_ = std::make_shared(); + eventKeyCommandHandler_ = std::make_shared(); + eventKeyCommandHandler_->SetNext(eventTestCommandHandler_); + + AAFwk::AbilityManagerClient::GetInstance()->SetCallback(TwoFingerLongTouchTest::AbilityCallback); + + abilityStarted_ = false; + err_ = ERR_OK; +} + +std::shared_ptr TwoFingerLongTouchTest::SetupPointerEvent(int32_t action, int32_t pointerId, + int32_t finger_num, int32_t dispX, int32_t dispY) +{ + std::shared_ptr pointerEvent = PointerEvent::Create(); + CHKPP(pointerEvent); + pointerEvent->SetSourceType(PointerEvent::SOURCE_TYPE_TOUCHSCREEN); + pointerEvent->SetPointerAction(action); + + pointerEvent->SetPointerId(pointerId); + + PointerEvent::PointerItem item1; + item1.SetPointerId(0); + item1.SetDisplayX(dispX); + item1.SetDisplayY(dispY); + pointerEvent->AddPointerItem(item1); + + if (finger_num == TWO_FINGERS) { + PointerEvent::PointerItem item2; + item2.SetPointerId(1); + item2.SetDisplayX(dispX + DISP_INITIAL_POS_X); + item2.SetDisplayY(dispY + DISP_INITIAL_POS_Y); + pointerEvent->AddPointerItem(item2); + } + + return pointerEvent; +} + +bool TwoFingerLongTouchTest::CreateTestJson(const std::string &contentJson) +{ + // Check test directory presence and create it if required + if (!std::filesystem::exists(TEST_DIR)) { + if (!std::filesystem::create_directory(TEST_DIR)) { + return false; + } + } + + std::fstream file; + file.open(TEST_JSON, std::ios::out | std::ios::trunc); + + if (!file.is_open()) { + return false; + } + file << contentJson; + + file.close(); + + return true; +} + +void TwoFingerLongTouchTest::Delay(std::chrono::milliseconds delayMs) +{ + std::this_thread::sleep_for(delayMs); +} + +void TwoFingerLongTouchTest::AbilityCallback(const AAFwk::Want &want, ErrCode err) +{ + EXPECT_EQ(want.bundleName_, BINDLE_NAME); + EXPECT_EQ(want.abilityName_, ABILITY_NAME); + EXPECT_EQ(want.action_, ACTION); + EXPECT_EQ(want.type_, TYPE); + EXPECT_EQ(want.deviceId_, DEVICE_ID); + EXPECT_EQ(want.uri_, URI); + + EXPECT_EQ(want.entities_.size(), ENTITY_NUM); + if (want.entities_.size() == ENTITY_NUM) { + for (unsigned i = 0; i < want.entities_.size(); ++i) { + std::string entity = want.entities_[i]; + std::string expected = ENTITY.data() + std::to_string(i + 1); + EXPECT_EQ(entity, expected); + } + } + + EXPECT_EQ(want.params_.size(), PARAMETERS_NUM); + if (want.params_.size() == PARAMETERS_NUM) { + for (unsigned i = 0; i < want.params_.size(); ++i) { + std::string key = KEY.data() + std::to_string(i + 1); + std::string value = VALUE.data() + std::to_string(i + 1); + auto item = want.params_.find(key); + EXPECT_NE(item, want.params_.end()); + + if (item != want.params_.end()) { + EXPECT_EQ(item->first, key); + EXPECT_EQ(item->second, value); + } + } + } + + TwoFingerLongTouchTest::abilityStarted_ = true; + TwoFingerLongTouchTest::err_ = err; +} + +/** + * @tc.name: TwoFingerLongTouchTest_001 + * @tc.desc: Test two finger long touch pointer event + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(TwoFingerLongTouchTest, TwoFingerLongTouchTest_001, TestSize.Level1) +{ + CALL_TEST_DEBUG; + ASSERT_NE(eventTestCommandHandler_, nullptr); + ASSERT_NE(eventKeyCommandHandler_, nullptr); + abilityStarted_ = false; + + auto pointerEvent1 = SetupPointerEvent(PointerEvent::POINTER_ACTION_DOWN, 0, 2); + ASSERT_NE(pointerEvent1, nullptr); + eventKeyCommandHandler_->HandleTouchEvent(pointerEvent1); + + Delay(WAIT_TIME_MS); + TimerMgr->ProcessTimers(); + + auto pointerEvent2 = SetupPointerEvent(PointerEvent::POINTER_ACTION_UP, 0, 2); + ASSERT_NE(pointerEvent2, nullptr); + eventKeyCommandHandler_->HandleTouchEvent(pointerEvent2); + + EXPECT_TRUE(abilityStarted_); + EXPECT_EQ(ERR_OK, err_); +} + +/** + * @tc.name: TwoFingerLongTouchTest_002 + * @tc.desc: Test one finger long touch pointer event + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(TwoFingerLongTouchTest, TwoFingerLongTouchTest_002, TestSize.Level1) +{ + CALL_TEST_DEBUG; + ASSERT_NE(eventTestCommandHandler_, nullptr); + ASSERT_NE(eventKeyCommandHandler_, nullptr); + abilityStarted_ = false; + + auto pointerEvent1 = SetupPointerEvent(PointerEvent::POINTER_ACTION_DOWN, 0, 1); + ASSERT_NE(pointerEvent1, nullptr); + eventKeyCommandHandler_->HandleTouchEvent(pointerEvent1); + + Delay(WAIT_TIME_MS); + TimerMgr->ProcessTimers(); + + auto pointerEvent2 = SetupPointerEvent(PointerEvent::POINTER_ACTION_UP, 0, 1); + ASSERT_NE(pointerEvent2, nullptr); + eventKeyCommandHandler_->HandleTouchEvent(pointerEvent2); + + EXPECT_FALSE(abilityStarted_); +} + +/** + * @tc.name: TwoFingerLongTouchTest_003 + * @tc.desc: Test two finger long touch gesture interruption + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(TwoFingerLongTouchTest, TwoFingerLongTouchTest_003, TestSize.Level1) +{ + CALL_TEST_DEBUG; + ASSERT_NE(eventTestCommandHandler_, nullptr); + ASSERT_NE(eventKeyCommandHandler_, nullptr); + abilityStarted_ = false; + + auto pointerEvent1 = SetupPointerEvent(PointerEvent::POINTER_ACTION_DOWN, 0, 2); + ASSERT_NE(pointerEvent1, nullptr); + eventKeyCommandHandler_->HandleTouchEvent(pointerEvent1); + + auto pointerEvent2 = SetupPointerEvent(PointerEvent::POINTER_ACTION_UP, 0, 2); + ASSERT_NE(pointerEvent2, nullptr); + eventKeyCommandHandler_->HandleTouchEvent(pointerEvent2); + + Delay(WAIT_TIME_MS); + TimerMgr->ProcessTimers(); + + EXPECT_FALSE(abilityStarted_); +} + +/** + * @tc.name: TwoFingerLongTouchTest_004 + * @tc.desc: Test two finger long touch gesture moving inside threshold + * (And one more unregistered event outside of threshold) + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(TwoFingerLongTouchTest, TwoFingerLongTouchTest_004, TestSize.Level1) +{ + CALL_TEST_DEBUG; + ASSERT_NE(eventTestCommandHandler_, nullptr); + ASSERT_NE(eventKeyCommandHandler_, nullptr); + abilityStarted_ = false; + + auto pointerEvent1 = SetupPointerEvent(PointerEvent::POINTER_ACTION_DOWN, 0, 1); + ASSERT_NE(pointerEvent1, nullptr); + eventKeyCommandHandler_->HandleTouchEvent(pointerEvent1); + + auto pointerEvent2 = SetupPointerEvent(PointerEvent::POINTER_ACTION_DOWN, 0, 2); + ASSERT_NE(pointerEvent2, nullptr); + eventKeyCommandHandler_->HandleTouchEvent(pointerEvent2); + + auto pointerEvent3 = SetupPointerEvent(PointerEvent::POINTER_ACTION_MOVE, 0, 2, DEFX + LESS_THEN_THRESHOLD, + DEFY + LESS_THEN_THRESHOLD); + ASSERT_NE(pointerEvent3, nullptr); + eventKeyCommandHandler_->HandleTouchEvent(pointerEvent3); + + auto pointerEvent4 = SetupPointerEvent(PointerEvent::POINTER_ACTION_MOVE, 2, 1, DEFX + GREATER_THEN_THRESHOLD, + DEFY + GREATER_THEN_THRESHOLD); + ASSERT_NE(pointerEvent4, nullptr); + eventKeyCommandHandler_->HandleTouchEvent(pointerEvent4); + + Delay(WAIT_TIME_MS); + TimerMgr->ProcessTimers(); + + EXPECT_TRUE(abilityStarted_); + EXPECT_EQ(ERR_OK, err_); +} + +/** + * @tc.name: TwoFingerLongTouchTest_005 + * @tc.desc: Test two finger long touch gesture moving outside threshold + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(TwoFingerLongTouchTest, TwoFingerLongTouchTest_005, TestSize.Level1) +{ + CALL_TEST_DEBUG; + ASSERT_NE(eventTestCommandHandler_, nullptr); + ASSERT_NE(eventKeyCommandHandler_, nullptr); + abilityStarted_ = false; + + auto pointerEvent1 = SetupPointerEvent(PointerEvent::POINTER_ACTION_DOWN, 0, 1); + ASSERT_NE(pointerEvent1, nullptr); + eventKeyCommandHandler_->HandleTouchEvent(pointerEvent1); + + auto pointerEvent2 = SetupPointerEvent(PointerEvent::POINTER_ACTION_DOWN, 0, 2); + ASSERT_NE(pointerEvent2, nullptr); + eventKeyCommandHandler_->HandleTouchEvent(pointerEvent2); + + auto pointerEvent3 = SetupPointerEvent(PointerEvent::POINTER_ACTION_MOVE, 0, 2, + DEFX + GREATER_THEN_THRESHOLD, DEFY + GREATER_THEN_THRESHOLD); + ASSERT_NE(pointerEvent3, nullptr); + eventKeyCommandHandler_->HandleTouchEvent(pointerEvent3); + + Delay(WAIT_TIME_MS); + TimerMgr->ProcessTimers(); + + EXPECT_FALSE(abilityStarted_); +} + +/** + * @tc.name: TwoFingerLongTouchTest_006 + * @tc.desc: Test to return error while ability is launching + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(TwoFingerLongTouchTest, TwoFingerLongTouchTest_006, TestSize.Level1) +{ + CALL_TEST_DEBUG; + ASSERT_NE(eventTestCommandHandler_, nullptr); + ASSERT_NE(eventKeyCommandHandler_, nullptr); + abilityStarted_ = false; + + AAFwk::AbilityManagerClient::GetInstance()->SetErrCode(ERR_INVALID_OPERATION); + + auto pointerEvent1 = SetupPointerEvent(PointerEvent::POINTER_ACTION_DOWN, 0, 2); + ASSERT_NE(pointerEvent1, nullptr); + eventKeyCommandHandler_->HandleTouchEvent(pointerEvent1); + + Delay(WAIT_TIME_MS); + TimerMgr->ProcessTimers(); + + auto pointerEvent2 = SetupPointerEvent(PointerEvent::POINTER_ACTION_UP, 0, 2); + ASSERT_NE(pointerEvent2, nullptr); + eventKeyCommandHandler_->HandleTouchEvent(pointerEvent2); + + AAFwk::AbilityManagerClient::GetInstance()->SetErrCode(ERR_OK); + + EXPECT_TRUE(abilityStarted_); + EXPECT_EQ(ERR_INVALID_OPERATION, err_); +} + +const std::string TEST_JSON_1 = ""; +const std::string TEST_JSON_2 = "{ \"TwoFingerGesture\" : [] }\n"; +const std::string TEST_JSON_3 = "{ \"TwoFingerGesture\" : {} }\n"; +const std::string TEST_JSON_4 = "{ \"TwoFingerGesture\" : {\"abilityStartDelay\" : 200} }\n"; +const std::string TEST_JSON_5 = "{ \"TwoFingerGesture\" : {\"abilityStartDelay\" : -1} }\n"; +const std::string TEST_JSON_6 = "{ \"TwoFingerGesture\" : {\"abilityStartDelay\" : \"abc\"} }\n"; +const std::string TEST_JSON_7 = "{ \"TwoFingerGesture\" : {\"abilityStartDelay\" : 200, \"ability\" : []} }\n"; +const std::string TEST_JSON_8 = "{ \"TwoFingerGesture\" : {\"abilityStartDelay\" : 200," + " \"ability\" : {\"bundleName\"}} }\n"; +const std::string TEST_JSON_9 = "{ \"TwoFingerGesture\" : {\"abilityStartDelay\" : 200," + " \"ability\" : {\"entities\" : {}}} }\n"; +const std::string TEST_JSON_10 = "{ \"TwoFingerGesture\" : {\"abilityStartDelay\" : 200," + " \"ability\" : {\"entities\" : [123]}} }\n"; +const std::string TEST_JSON_11 = "{ \"TwoFingerGesture\" : {\"abilityStartDelay\" : 200," + " \"ability\" : {\"params\" : {}}} }\n"; +const std::string TEST_JSON_12 = "{ \"TwoFingerGesture\" : {\"abilityStartDelay\" : 200," + " \"ability\" : {\"params\" : [[]]}} }\n"; +const std::string TEST_JSON_13 = "{ \"TwoFingerGesture\" : {\"abilityStartDelay\" : 200," + " \"ability\" : {\"params\" : [{}]}} }\n"; +const std::string TEST_JSON_14 = "{ \"TwoFingerGesture\" : {\"abilityStartDelay\" : 200," + " \"ability\" : {\"params\" : [{\"key\" : \"key1\"}]}} }\n"; +const std::string TEST_JSON_15 = "{ \"TwoFingerGesture\" : {\"abilityStartDelay\" : 200, \"ability\" : {}} }\n"; + +/** + * @tc.name: TwoFingerLongTouchTest_007 + * @tc.desc: Test JSON parsing error branches + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(TwoFingerLongTouchTest, TwoFingerLongTouchTest_007, TestSize.Level1) +{ + CALL_TEST_DEBUG; + ASSERT_NE(eventTestCommandHandler_, nullptr); + ASSERT_NE(eventKeyCommandHandler_, nullptr); + abilityStarted_ = false; + + ASSERT_TRUE(CreateTestJson(TEST_JSON_1)); + ASSERT_FALSE(eventKeyCommandHandler_->ParseJson(TEST_JSON)); + + ASSERT_TRUE(CreateTestJson(TEST_JSON_2)); + ASSERT_FALSE(eventKeyCommandHandler_->ParseJson(TEST_JSON)); + + ASSERT_TRUE(CreateTestJson(TEST_JSON_3)); + ASSERT_FALSE(eventKeyCommandHandler_->ParseJson(TEST_JSON)); + + ASSERT_TRUE(CreateTestJson(TEST_JSON_4)); + ASSERT_FALSE(eventKeyCommandHandler_->ParseJson(TEST_JSON)); + + ASSERT_TRUE(CreateTestJson(TEST_JSON_5)); + ASSERT_FALSE(eventKeyCommandHandler_->ParseJson(TEST_JSON)); + + ASSERT_TRUE(CreateTestJson(TEST_JSON_6)); + ASSERT_FALSE(eventKeyCommandHandler_->ParseJson(TEST_JSON)); + + ASSERT_TRUE(CreateTestJson(TEST_JSON_7)); + ASSERT_FALSE(eventKeyCommandHandler_->ParseJson(TEST_JSON)); + + ASSERT_TRUE(CreateTestJson(TEST_JSON_8)); + ASSERT_FALSE(eventKeyCommandHandler_->ParseJson(TEST_JSON)); + + ASSERT_TRUE(CreateTestJson(TEST_JSON_9)); + ASSERT_FALSE(eventKeyCommandHandler_->ParseJson(TEST_JSON)); + + ASSERT_TRUE(CreateTestJson(TEST_JSON_10)); + ASSERT_FALSE(eventKeyCommandHandler_->ParseJson(TEST_JSON)); + + ASSERT_TRUE(CreateTestJson(TEST_JSON_11)); + ASSERT_FALSE(eventKeyCommandHandler_->ParseJson(TEST_JSON)); + + ASSERT_TRUE(CreateTestJson(TEST_JSON_12)); + ASSERT_FALSE(eventKeyCommandHandler_->ParseJson(TEST_JSON)); + + ASSERT_TRUE(CreateTestJson(TEST_JSON_13)); + ASSERT_FALSE(eventKeyCommandHandler_->ParseJson(TEST_JSON)); + + ASSERT_TRUE(CreateTestJson(TEST_JSON_14)); + ASSERT_FALSE(eventKeyCommandHandler_->ParseJson(TEST_JSON)); + + ASSERT_TRUE(CreateTestJson(TEST_JSON_15)); + ASSERT_TRUE(eventKeyCommandHandler_->ParseJson(TEST_JSON)); +} + +} // namespace MMI +} // namespace OHOS diff --git a/service/libinput_adapter/include/libinput_adapter.h b/service/libinput_adapter/include/libinput_adapter.h index 42bbe41d2c7710a8934db8c2ecbf26a219eeaea7..4594903c480acfd511826f1b87ed525b6238cfe1 100644 --- a/service/libinput_adapter/include/libinput_adapter.h +++ b/service/libinput_adapter/include/libinput_adapter.h @@ -26,7 +26,7 @@ namespace OHOS { namespace MMI { -typedef std::function FunInputEvent; +typedef std::function FunInputEvent; class LibinputAdapter final { public: static int32_t DeviceLedUpdate(struct libinput_device *device, int32_t funcKey, bool isEnable); diff --git a/service/libinput_adapter/src/libinput_adapter.cpp b/service/libinput_adapter/src/libinput_adapter.cpp index a3af02b5c3aa97d8980d2de3b46b931ca823ff1e..51dd8d3deca29af97eef855417e176679c3f7b6e 100644 --- a/service/libinput_adapter/src/libinput_adapter.cpp +++ b/service/libinput_adapter/src/libinput_adapter.cpp @@ -147,10 +147,14 @@ void LibinputAdapter::OnEventHandler() CALL_DEBUG_ENTER; CHKPV(funInputEvent_); libinput_event *event = nullptr; + int64_t frameTime = GetSysClockTime(); while ((event = libinput_get_event(input_))) { - funInputEvent_(event); + funInputEvent_(event, frameTime); libinput_event_destroy(event); } + if (event == nullptr) { + funInputEvent_(nullptr, 0); + } } void LibinputAdapter::ReloadDevice() diff --git a/service/module_loader/src/mmi_service.cpp b/service/module_loader/src/mmi_service.cpp index b9e6319befa3c6485cf0952de86b7604eb958738..6bf4b0ab1e32d8a3e0f39f22f6b1f0c1a812c905 100644 --- a/service/module_loader/src/mmi_service.cpp +++ b/service/module_loader/src/mmi_service.cpp @@ -175,9 +175,10 @@ bool MMIService::IsRunning() const bool MMIService::InitLibinputService() { - if (!(libinputAdapter_.Init(std::bind(&InputEventHandler::OnEvent, InputHandler, std::placeholders::_1)))) { - MMI_HILOGE("Libinput init, bind failed"); - return false; + if (!(libinputAdapter_.Init(std::bind(&InputEventHandler::OnEvent, InputHandler, + std::placeholders::_1, std::placeholders::_2)))) { + MMI_HILOGE("Libinput init, bind failed"); + return false; } auto inputFds = libinputAdapter_.GetInputFds(); for (auto fd : inputFds) {