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