blob: e9f8cca7cd77370cd5ce8241587bc5b6af74f094 [file] [log] [blame]
dmazzoni16d2dec2016-12-19 23:27:571// Copyright 2016 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
James Cook864dabc2018-02-14 18:12:085#include "ash/accessibility/touch_accessibility_enabler.h"
dmazzoni16d2dec2016-12-19 23:27:576
7#include <math.h>
8
9#include <utility>
10
11#include "base/logging.h"
12#include "base/metrics/user_metrics.h"
13#include "base/time/default_tick_clock.h"
14#include "ui/aura/window.h"
15#include "ui/aura/window_event_dispatcher.h"
16#include "ui/aura/window_tree_host.h"
17#include "ui/events/event.h"
18#include "ui/events/event_processor.h"
19#include "ui/events/event_utils.h"
20
James Cook864dabc2018-02-14 18:12:0821namespace ash {
dmazzoni16d2dec2016-12-19 23:27:5722
23namespace {
24
25// Delay between timer callbacks. Each one plays a tick sound.
26constexpr int kTimerDelayInMS = 500;
27
28// The number of ticks of the timer before the first sound is generated.
29constexpr int kTimerTicksOfFirstSoundFeedback = 6;
30
31// The number of ticks of the timer before toggling spoken feedback.
32constexpr int kTimerTicksToToggleSpokenFeedback = 10;
33
34} // namespace
35
36TouchAccessibilityEnabler::TouchAccessibilityEnabler(
37 aura::Window* root_window,
38 TouchAccessibilityEnablerDelegate* delegate)
39 : root_window_(root_window),
40 delegate_(delegate),
41 state_(NO_FINGERS_DOWN),
Dominic Mazzoni51c65f22017-08-22 19:36:0642 tick_clock_(nullptr),
43 weak_factory_(this) {
dmazzoni16d2dec2016-12-19 23:27:5744 DCHECK(root_window);
45 DCHECK(delegate);
Dominic Mazzoni51c65f22017-08-22 19:36:0646 AddEventHandler();
dmazzoni16d2dec2016-12-19 23:27:5747}
48
49TouchAccessibilityEnabler::~TouchAccessibilityEnabler() {
Dominic Mazzoni51c65f22017-08-22 19:36:0650 RemoveEventHandler();
51}
52
53void TouchAccessibilityEnabler::RemoveEventHandler() {
54 if (event_handler_installed_) {
55 root_window_->RemovePreTargetHandler(this);
56 event_handler_installed_ = false;
57 ResetToNoFingersDown();
58 }
59}
60
61void TouchAccessibilityEnabler::AddEventHandler() {
62 if (!event_handler_installed_) {
63 root_window_->AddPreTargetHandler(this);
64 event_handler_installed_ = true;
65 ResetToNoFingersDown();
66 }
dmazzoni16d2dec2016-12-19 23:27:5767}
68
69void TouchAccessibilityEnabler::OnTouchEvent(ui::TouchEvent* event) {
Dominic Mazzoni51c65f22017-08-22 19:36:0670 DCHECK(!(event->flags() & ui::EF_TOUCH_ACCESSIBILITY));
71 HandleTouchEvent(*event);
dmazzoni16d2dec2016-12-19 23:27:5772}
73
74void TouchAccessibilityEnabler::HandleTouchEvent(const ui::TouchEvent& event) {
75 DCHECK(!(event.flags() & ui::EF_TOUCH_ACCESSIBILITY));
76 const ui::EventType type = event.type();
77 const gfx::PointF& location = event.location_f();
lanwei31739e48a2017-02-15 21:29:5578 const int touch_id = event.pointer_details().id;
dmazzoni16d2dec2016-12-19 23:27:5779
80 if (type == ui::ET_TOUCH_PRESSED) {
81 touch_locations_.insert(std::pair<int, gfx::PointF>(touch_id, location));
82 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
83 auto iter = touch_locations_.find(touch_id);
84
85 // Can happen if this object is constructed while fingers were down.
86 if (iter == touch_locations_.end())
87 return;
88
89 touch_locations_.erase(touch_id);
90 } else if (type == ui::ET_TOUCH_MOVED) {
91 auto iter = touch_locations_.find(touch_id);
92
93 // Can happen if this object is constructed while fingers were down.
94 if (iter == touch_locations_.end())
95 return;
96
97 float delta = (location - iter->second).Length();
98 if (delta > gesture_detector_config_.double_tap_slop) {
99 state_ = WAIT_FOR_NO_FINGERS;
100 CancelTimer();
101 return;
102 }
103 } else {
sky9fe49ad2017-03-20 16:16:45104 NOTREACHED() << "Unexpected event type received: " << event.GetName();
dmazzoni16d2dec2016-12-19 23:27:57105 return;
106 }
107
108 if (touch_locations_.size() == 0) {
109 state_ = NO_FINGERS_DOWN;
110 CancelTimer();
111 return;
112 }
113
114 if (touch_locations_.size() > 2) {
115 state_ = WAIT_FOR_NO_FINGERS;
116 CancelTimer();
117 return;
118 }
119
120 if (state_ == NO_FINGERS_DOWN && event.type() == ui::ET_TOUCH_PRESSED) {
121 state_ = ONE_FINGER_DOWN;
122 } else if (state_ == ONE_FINGER_DOWN &&
123 event.type() == ui::ET_TOUCH_PRESSED) {
124 state_ = TWO_FINGERS_DOWN;
125 two_finger_start_time_ = Now();
126 StartTimer();
dmazzonidf9a1e922017-04-13 05:10:44127 delegate_->OnTwoFingerTouchStart();
dmazzoni16d2dec2016-12-19 23:27:57128 }
129}
130
Dominic Mazzoni51c65f22017-08-22 19:36:06131base::WeakPtr<TouchAccessibilityEnabler>
132TouchAccessibilityEnabler::GetWeakPtr() {
133 return weak_factory_.GetWeakPtr();
134}
135
dmazzoni16d2dec2016-12-19 23:27:57136base::TimeTicks TouchAccessibilityEnabler::Now() {
137 if (tick_clock_) {
138 // This is the same as what EventTimeForNow() does, but here we do it
139 // with a clock that can be replaced with a simulated clock for tests.
140 return tick_clock_->NowTicks();
141 }
142 return ui::EventTimeForNow();
143}
144
145void TouchAccessibilityEnabler::StartTimer() {
146 if (timer_.IsRunning())
147 return;
148
149 timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kTimerDelayInMS),
James Cook864dabc2018-02-14 18:12:08150 this, &TouchAccessibilityEnabler::OnTimer);
dmazzoni16d2dec2016-12-19 23:27:57151}
152
153void TouchAccessibilityEnabler::CancelTimer() {
dmazzonidf9a1e922017-04-13 05:10:44154 if (timer_.IsRunning()) {
dmazzoni16d2dec2016-12-19 23:27:57155 timer_.Stop();
dmazzonidf9a1e922017-04-13 05:10:44156 delegate_->OnTwoFingerTouchStop();
157 }
dmazzoni16d2dec2016-12-19 23:27:57158}
159
160void TouchAccessibilityEnabler::OnTimer() {
161 base::TimeTicks now = Now();
162 double tick_count_f =
163 (now - two_finger_start_time_).InMillisecondsF() / kTimerDelayInMS;
164 int tick_count = roundf(tick_count_f);
165
166 if (tick_count == kTimerTicksOfFirstSoundFeedback) {
167 base::RecordAction(
168 base::UserMetricsAction("Accessibility.TwoFingersHeldDown"));
169 }
170
171 if (tick_count >= kTimerTicksOfFirstSoundFeedback &&
172 tick_count < kTimerTicksToToggleSpokenFeedback) {
173 delegate_->PlaySpokenFeedbackToggleCountdown(tick_count);
174 }
175 if (tick_count == kTimerTicksToToggleSpokenFeedback) {
176 delegate_->ToggleSpokenFeedback();
177 state_ = WAIT_FOR_NO_FINGERS;
178 }
179}
180
Dominic Mazzoni51c65f22017-08-22 19:36:06181void TouchAccessibilityEnabler::ResetToNoFingersDown() {
182 state_ = NO_FINGERS_DOWN;
183 touch_locations_.clear();
184 CancelTimer();
185}
186
James Cook864dabc2018-02-14 18:12:08187} // namespace ash