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