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