1 /*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed On an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "accessibility_gesture_recognizer.h"
17 #include "hilog_wrapper.h"
18 #include <cinttypes>
19
20 namespace OHOS {
21 namespace Accessibility {
22 namespace {
23 constexpr int32_t LIMIT_SIZE_TWO = 2;
24 constexpr int32_t LIMIT_SIZE_THREE = 3;
25 constexpr int32_t POINTER_COUNT_1 = 1;
26 constexpr float EPSINON = 0.0001f;
27 constexpr float TOUCH_SLOP = 8.0f;
28 } // namespace
29
GestureHandler(const std::shared_ptr<AppExecFwk::EventRunner> &runner, AccessibilityGestureRecognizer &server)30 GestureHandler::GestureHandler(const std::shared_ptr<AppExecFwk::EventRunner> &runner,
31 AccessibilityGestureRecognizer &server) : AppExecFwk::EventHandler(runner), server_(server)
32 {
33 }
34
ProcessEvent(const AppExecFwk::InnerEvent::Pointer &event)35 void GestureHandler::ProcessEvent(const AppExecFwk::InnerEvent::Pointer &event)
36 {
37 HILOG_DEBUG();
38 if (!event) {
39 HILOG_ERROR("event is null");
40 return;
41 }
42
43 switch (event->GetInnerEventId()) {
44 case AccessibilityGestureRecognizer::LONG_PRESS_MSG:
45 RemoveEvent(AccessibilityGestureRecognizer::SINGLE_TAP_MSG);
46 server_.SetIsLongpress(true);
47 server_.MaybeRecognizeLongPress(*server_.GetCurDown());
48 break;
49 case AccessibilityGestureRecognizer::SINGLE_TAP_MSG:
50 if (!server_.GetContinueDown()) {
51 server_.SingleTapDetected();
52 }
53 break;
54 default:
55 break;
56 }
57 }
58
GetDoubleTapMoveThreshold(float densityDpi)59 float AccessibilityGestureRecognizer::GetDoubleTapMoveThreshold(float densityDpi)
60 {
61 return densityDpi * (1.0f / 25.4f) * MM_PER_CM;
62 }
63
AccessibilityGestureRecognizer()64 AccessibilityGestureRecognizer::AccessibilityGestureRecognizer()
65 {
66 HILOG_DEBUG();
67 #ifdef OHOS_BUILD_ENABLE_DISPLAY_MANAGER
68 AccessibilityDisplayManager &displayMgr = Singleton<AccessibilityDisplayManager>::GetInstance();
69 auto display = displayMgr.GetDefaultDisplay();
70 if (!display) {
71 HILOG_ERROR("get display is nullptr");
72 return;
73 }
74
75 threshold_ = GetDoubleTapMoveThreshold(display->GetDpi());
76 xMinPixels_ = MIN_PIXELS(display->GetWidth());
77 yMinPixels_ = MIN_PIXELS(display->GetHeight());
78
79 float densityPixels = display->GetVirtualPixelRatio();
80 int32_t slop = static_cast<int32_t>(densityPixels * DOUBLE_TAP_SLOP + 0.5f);
81 doubleTapScaledSlop_ = slop * slop;
82 #else
83 HILOG_DEBUG("not support display manager");
84 threshold_ = 1;
85 xMinPixels_ = 1;
86 yMinPixels_ = 1;
87 int32_t slop = static_cast<int32_t>(1 * DOUBLE_TAP_SLOP + 0.5f);
88 doubleTapScaledSlop_ = slop * slop;
89 #endif
90
91 runner_ = Singleton<AccessibleAbilityManagerService>::GetInstance().GetMainRunner();
92 if (!runner_) {
93 HILOG_ERROR("get runner failed");
94 return;
95 }
96 handler_ = std::make_shared<GestureHandler>(runner_, *this);
97 if (!handler_) {
98 HILOG_ERROR("create event handler failed");
99 return;
100 }
101 }
102
RegisterListener(AccessibilityGestureRecognizeListener& listener)103 void AccessibilityGestureRecognizer::RegisterListener(AccessibilityGestureRecognizeListener& listener)
104 {
105 HILOG_DEBUG();
106
107 listener_ = &listener;
108 }
109
UnregisterListener()110 void AccessibilityGestureRecognizer::UnregisterListener()
111 {
112 HILOG_DEBUG();
113
114 listener_ = nullptr;
115 }
116
OnPointerEvent(MMI::PointerEvent &event)117 bool AccessibilityGestureRecognizer::OnPointerEvent(MMI::PointerEvent &event)
118 {
119 HILOG_DEBUG();
120
121 switch (event.GetPointerAction()) {
122 case MMI::PointerEvent::POINTER_ACTION_DOWN:
123 if (isDoubleTap_ && isLongpress_) {
124 HILOG_INFO("isDoubleTap and longpress, on down event");
125 return false;
126 }
127 if (event.GetPointerIds().size() == POINTER_COUNT_1) {
128 HandleTouchDownEvent(event);
129 } else {
130 Clear();
131 isRecognizingGesture_ = false;
132 isGestureStarted_ = false;
133 pointerRoute_.clear();
134 }
135 break;
136 case MMI::PointerEvent::POINTER_ACTION_MOVE:
137 if (isDoubleTap_ && isLongpress_) {
138 HILOG_DEBUG("isDoubleTap and isLongpress, send move event to Multimodel.");
139 return false;
140 }
141 return HandleTouchMoveEvent(event);
142 case MMI::PointerEvent::POINTER_ACTION_UP:
143 if (event.GetPointerIds().size() == POINTER_COUNT_1) {
144 return HandleTouchUpEvent(event);
145 }
146 break;
147 case MMI::PointerEvent::POINTER_ACTION_CANCEL:
148 Clear();
149 break;
150 default:
151 break;
152 }
153 if (!isRecognizingGesture_) {
154 return false;
155 }
156 return StandardGestureRecognizer(event);
157 }
158
Clear()159 void AccessibilityGestureRecognizer::Clear()
160 {
161 HILOG_DEBUG();
162
163 isFirstTapUp_ = false;
164 isDoubleTap_ = false;
165 isGestureStarted_ = false;
166 isRecognizingGesture_ = false;
167 pointerRoute_.clear();
168 continueDown_ = false;
169 StandardGestureCanceled();
170 }
171
HandleTouchDownEvent(MMI::PointerEvent &event)172 void AccessibilityGestureRecognizer::HandleTouchDownEvent(MMI::PointerEvent &event)
173 {
174 HILOG_DEBUG();
175
176 Pointer mp;
177 MMI::PointerEvent::PointerItem pointerIterm;
178 if (!event.GetPointerItem(event.GetPointerId(), pointerIterm)) {
179 HILOG_WARN("get GetPointerItem(%{public}d) failed", event.GetPointerId());
180 }
181 mp.px_ = static_cast<float>(pointerIterm.GetDisplayX());
182 mp.py_ = static_cast<float>(pointerIterm.GetDisplayY());
183 isDoubleTap_ = false;
184 isRecognizingGesture_ = true;
185 isGestureStarted_ = false;
186 pointerRoute_.clear();
187 pointerRoute_.push_back(mp);
188 prePointer_ = pointerIterm;
189 startPointer_ = pointerIterm;
190 startTime_ = event.GetActionTime();
191 }
192
AddSwipePosition(MMI::PointerEvent::PointerItem &pointerIterm)193 void AccessibilityGestureRecognizer::AddSwipePosition(MMI::PointerEvent::PointerItem &pointerIterm)
194 {
195 HILOG_DEBUG();
196
197 Pointer mp;
198 prePointer_ = pointerIterm;
199 mp.px_ = pointerIterm.GetDisplayX();
200 mp.py_ = pointerIterm.GetDisplayY();
201 pointerRoute_.push_back(mp);
202 }
203
HandleTouchMoveEvent(MMI::PointerEvent &event)204 bool AccessibilityGestureRecognizer::HandleTouchMoveEvent(MMI::PointerEvent &event)
205 {
206 HILOG_DEBUG();
207
208 MMI::PointerEvent::PointerItem pointerIterm;
209 if (!event.GetPointerItem(event.GetPointerId(), pointerIterm)) {
210 HILOG_ERROR("get GetPointerItem(%{public}d) failed", event.GetPointerId());
211 return false;
212 }
213 int64_t eventTime = event.GetActionTime();
214 float offsetX = startPointer_.GetDisplayX() - pointerIterm.GetDisplayX();
215 float offsetY = startPointer_.GetDisplayY() - pointerIterm.GetDisplayY();
216 double duration = hypot(offsetX, offsetY);
217 if (isRecognizingGesture_) {
218 if (isDoubleTap_ && duration > TOUCH_SLOP) {
219 HILOG_DEBUG("Cancel double tap event because the finger moves beyond preset slop.");
220 isRecognizingGesture_ = false;
221 isDoubleTap_ = false;
222 return listener_->OnCancelled(event);
223 } else if (duration > threshold_) {
224 startPointer_ = pointerIterm;
225 startTime_ = eventTime;
226 isFirstTapUp_ = false;
227 isDoubleTap_ = false;
228 if (!isGestureStarted_) {
229 isGestureStarted_ = true;
230 listener_->OnStarted();
231 return false;
232 }
233 } else if (!isFirstTapUp_) {
234 int64_t durationTime = eventTime - startTime_;
235 int64_t thresholdTime = isGestureStarted_ ?
236 GESTURE_STARTED_TIME_THRESHOLD : GESTURE_NOT_STARTED_TIME_THRESHOLD;
237 if (durationTime > thresholdTime) {
238 isRecognizingGesture_ = false;
239 isGestureStarted_ = false;
240 pointerRoute_.clear();
241 return listener_->OnCancelled(event);
242 }
243 }
244 if ((abs(pointerIterm.GetDisplayX() - prePointer_.GetDisplayX())) >= xMinPixels_ ||
245 (abs(pointerIterm.GetDisplayY() - prePointer_.GetDisplayY())) >= yMinPixels_) {
246 AddSwipePosition(pointerIterm);
247 }
248 }
249 if (!isRecognizingGesture_) {
250 return false;
251 }
252
253 return StandardGestureRecognizer(event);
254 }
255
HandleTouchUpEvent(MMI::PointerEvent &event)256 bool AccessibilityGestureRecognizer::HandleTouchUpEvent(MMI::PointerEvent &event)
257 {
258 HILOG_DEBUG();
259
260 Pointer mp;
261 MMI::PointerEvent::PointerItem pointerIterm;
262 if (!event.GetPointerItem(event.GetPointerId(), pointerIterm)) {
263 HILOG_WARN("get GetPointerItem(%{public}d) failed", event.GetPointerId());
264 }
265
266 if (isDoubleTap_) {
267 if (isLongpress_) {
268 HILOG_DEBUG("up event, isDoubleTap and longpress.");
269 return false;
270 } else {
271 HILOG_DEBUG();
272 return DoubleTapRecognized(event);
273 }
274 }
275 if (isGestureStarted_) {
276 if ((abs(pointerIterm.GetDisplayX() - prePointer_.GetDisplayX())) >= xMinPixels_ ||
277 (abs(pointerIterm.GetDisplayY() - prePointer_.GetDisplayY())) >= yMinPixels_) {
278 HILOG_DEBUG("Add position to pointer route.");
279 mp.px_ = pointerIterm.GetDisplayX();
280 mp.py_ = pointerIterm.GetDisplayY();
281 pointerRoute_.push_back(mp);
282 }
283 return recognizeDirectionGesture(event);
284 }
285 if (!isRecognizingGesture_) {
286 return false;
287 }
288 return StandardGestureRecognizer(event);
289 }
290
StandardGestureRecognizer(MMI::PointerEvent &event)291 bool AccessibilityGestureRecognizer::StandardGestureRecognizer(MMI::PointerEvent &event)
292 {
293 HILOG_DEBUG();
294
295 switch (event.GetPointerAction()) {
296 case MMI::PointerEvent::POINTER_ACTION_DOWN:
297 if (event.GetPointerIds().size() == POINTER_COUNT_1) {
298 if (pCurDown_ && pPreUp_ && isDoubleTap(event)) {
299 HILOG_DEBUG("Double tap is recognized");
300 isDoubleTapdetecting_ = true;
301 isDoubleTap_ = true;
302 } else {
303 handler_->SendEvent(SINGLE_TAP_MSG, 0, DOUBLE_TAP_TIMEOUT / US_TO_MS);
304 }
305 pCurDown_ = std::make_shared<MMI::PointerEvent>(event);
306 isTapDown_ = true;
307 continueDown_ = true;
308 isLongpress_ = false;
309 handler_->RemoveEvent(LONG_PRESS_MSG);
310 handler_->SendEvent(LONG_PRESS_MSG, 0, LONG_PRESS_TIMEOUT / US_TO_MS);
311 } else {
312 StandardGestureCanceled();
313 }
314 break;
315 case MMI::PointerEvent::POINTER_ACTION_UP:
316 if (event.GetPointerIds().size() == POINTER_COUNT_1) {
317 continueDown_ = false;
318 if (isLongpress_) {
319 handler_->RemoveEvent(SINGLE_TAP_MSG);
320 isLongpress_ = false;
321 } else if (!isDoubleTapdetecting_ && isTapDown_) {
322 isFirstTapUp_ = true;
323 }
324 pPreUp_ = std::make_unique<MMI::PointerEvent>(event);
325 isDoubleTapdetecting_ = false;
326 handler_->RemoveEvent(LONG_PRESS_MSG);
327 }
328 break;
329 default:
330 break;
331 }
332 return false;
333 }
334
StandardGestureCanceled()335 void AccessibilityGestureRecognizer::StandardGestureCanceled()
336 {
337 HILOG_DEBUG();
338
339 handler_->RemoveEvent(LONG_PRESS_MSG);
340 handler_->RemoveEvent(SINGLE_TAP_MSG);
341 isLongpress_ = false;
342 isDoubleTapdetecting_ = false;
343 isTapDown_ = false;
344 isDoubleTap_ = false;
345 }
346
SingleTapDetected()347 void AccessibilityGestureRecognizer::SingleTapDetected()
348 {
349 HILOG_DEBUG();
350
351 Clear();
352 }
353
MaybeRecognizeLongPress(MMI::PointerEvent &event)354 void AccessibilityGestureRecognizer::MaybeRecognizeLongPress(MMI::PointerEvent &event)
355 {
356 HILOG_DEBUG();
357 }
358
DoubleTapRecognized(MMI::PointerEvent &event)359 bool AccessibilityGestureRecognizer::DoubleTapRecognized(MMI::PointerEvent &event)
360 {
361 HILOG_DEBUG();
362
363 Clear();
364 return listener_->OnDoubleTap(event);
365 }
366
recognizeDirectionGesture(MMI::PointerEvent &event)367 bool AccessibilityGestureRecognizer::recognizeDirectionGesture(MMI::PointerEvent &event)
368 {
369 HILOG_DEBUG();
370 if (!listener_) {
371 HILOG_ERROR("listener_ is nullptr.");
372 return false;
373 }
374
375 if (pointerRoute_.size() < LIMIT_SIZE_TWO) {
376 return listener_->OnCancelled(event);
377 }
378
379 // Check the angle of the most recent motion vector versus the preceding motion vector,
380 // segment the line if the angle is about 90 degrees.
381 std::vector<Pointer> pointerPath = GetPointerPath(pointerRoute_);
382
383 if (pointerPath.size() == LIMIT_SIZE_TWO) {
384 int32_t swipeDirection = GetSwipeDirection(pointerPath[0], pointerPath[1]);
385 return listener_->OnCompleted(GESTURE_DIRECTION[swipeDirection]);
386 } else if (pointerPath.size() == LIMIT_SIZE_THREE) {
387 int32_t swipeDirectionH = GetSwipeDirection(pointerPath[0], pointerPath[1]);
388 int32_t swipeDirectionHV = GetSwipeDirection(pointerPath[1], pointerPath[2]);
389 return listener_->OnCompleted(GESTURE_DIRECTION_TO_ID[swipeDirectionH][swipeDirectionHV]);
390 }
391 return listener_->OnCancelled(event);
392 }
393
GetSwipeDirection(Pointer firstP, Pointer secondP)394 int32_t AccessibilityGestureRecognizer::GetSwipeDirection(Pointer firstP, Pointer secondP)
395 {
396 float offsetX = secondP.px_ - firstP.px_;
397 float offsetY = secondP.py_ - firstP.py_;
398 if (abs(offsetX) > abs(offsetY)) {
399 return offsetX > EPSINON ? SWIPE_RIGHT : SWIPE_LEFT;
400 } else {
401 return offsetY < EPSINON ? SWIPE_UP : SWIPE_DOWN;
402 }
403 }
404
GetPointerPath(std::vector<Pointer> &route)405 std::vector<Pointer> AccessibilityGestureRecognizer::GetPointerPath(std::vector<Pointer> &route)
406 {
407 HILOG_DEBUG();
408
409 std::vector<Pointer> pointerPath;
410 Pointer firstSeparation = route[0];
411 Pointer nextPoint;
412 Pointer newSeparation;
413 float xUnitVector = 0;
414 float yUnitVector = 0;
415 float xVector = 0;
416 float yVector = 0;
417 float vectorLength = 0;
418 int32_t numSinceFirstSep = 0;
419
420 pointerPath.push_back(firstSeparation);
421 for (size_t i = 1; i < route.size(); i++) {
422 nextPoint = route[i];
423 if (numSinceFirstSep > 0) {
424 xVector = xUnitVector / numSinceFirstSep;
425 yVector = yUnitVector / numSinceFirstSep;
426 newSeparation.px_ = vectorLength * xVector + firstSeparation.px_;
427 newSeparation.py_ = vectorLength * yVector + firstSeparation.py_;
428
429 float xNextUnitVector = nextPoint.px_ - newSeparation.px_;
430 float yNextUnitVector = nextPoint.py_ - newSeparation.py_;
431 float nextVectorLength = hypot(xNextUnitVector, yNextUnitVector);
432 if (nextVectorLength > EPSINON) {
433 xNextUnitVector /= nextVectorLength;
434 yNextUnitVector /= nextVectorLength;
435 }
436
437 if ((xVector * xNextUnitVector + yVector * yNextUnitVector) < DEGREES_THRESHOLD) {
438 pointerPath.push_back(newSeparation);
439 firstSeparation = newSeparation;
440 xUnitVector = 0;
441 yUnitVector = 0;
442 numSinceFirstSep = 0;
443 }
444 }
445 xVector = nextPoint.px_ - firstSeparation.px_;
446 yVector = nextPoint.py_ - firstSeparation.py_;
447 vectorLength = hypot(xVector, yVector);
448 numSinceFirstSep += 1;
449 if (vectorLength > EPSINON) {
450 xUnitVector += xVector / vectorLength;
451 yUnitVector += yVector / vectorLength;
452 }
453 }
454 pointerPath.push_back(nextPoint);
455 return pointerPath;
456 }
457
isDoubleTap(MMI::PointerEvent &event)458 bool AccessibilityGestureRecognizer::isDoubleTap(MMI::PointerEvent &event)
459 {
460 HILOG_DEBUG();
461 int64_t durationTime = event.GetActionTime() - pPreUp_->GetActionTime();
462 if (!(durationTime <= DOUBLE_TAP_TIMEOUT)) {
463 HILOG_WARN("durationTime[%{public}" PRId64 "] is wrong", durationTime);
464 return false;
465 }
466
467 MMI::PointerEvent::PointerItem curPI;
468 if (!event.GetPointerItem(event.GetPointerId(), curPI)) {
469 HILOG_WARN("get GetPointerItem(%{public}d) failed", event.GetPointerId());
470 }
471
472 MMI::PointerEvent::PointerItem firstPI;
473 pCurDown_->GetPointerItem(pCurDown_->GetPointerId(), firstPI);
474 int32_t durationX = firstPI.GetDisplayX() - curPI.GetDisplayX();
475 int32_t durationY = firstPI.GetDisplayY() - curPI.GetDisplayY();
476
477 return (durationX * durationX + durationY * durationY < doubleTapScaledSlop_);
478 }
479 } // namespace Accessibility
480 } // namespace OHOS
481