Index: ui/chromeos/touch_accessibility_enabler.cc |
diff --git a/ui/chromeos/touch_accessibility_enabler.cc b/ui/chromeos/touch_accessibility_enabler.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..07d5c80005516624d0102259d5707ff9b41e162c |
--- /dev/null |
+++ b/ui/chromeos/touch_accessibility_enabler.cc |
@@ -0,0 +1,158 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "ui/chromeos/touch_accessibility_enabler.h" |
+ |
+#include <math.h> |
+ |
+#include <utility> |
+ |
+#include "base/logging.h" |
+#include "base/metrics/user_metrics.h" |
+#include "base/time/default_tick_clock.h" |
+#include "ui/aura/window.h" |
+#include "ui/aura/window_event_dispatcher.h" |
+#include "ui/aura/window_tree_host.h" |
+#include "ui/events/event.h" |
+#include "ui/events/event_processor.h" |
+#include "ui/events/event_utils.h" |
+ |
+namespace ui { |
+ |
+namespace { |
+ |
+// Delay between timer callbacks. Each one plays a tick sound. |
+constexpr int kTimerDelayInMS = 500; |
+ |
+// The number of ticks of the timer before the first sound is generated. |
+constexpr int kTimerTicksOfFirstSoundFeedback = 6; |
+ |
+// The number of ticks of the timer before toggling spoken feedback. |
+constexpr int kTimerTicksToToggleSpokenFeedback = 10; |
+ |
+} // namespace |
+ |
+TouchAccessibilityEnabler::TouchAccessibilityEnabler( |
+ aura::Window* root_window, |
+ TouchAccessibilityEnablerDelegate* delegate) |
+ : root_window_(root_window), |
+ delegate_(delegate), |
+ state_(NO_FINGERS_DOWN), |
+ tick_clock_(NULL) { |
+ DCHECK(root_window); |
+ DCHECK(delegate); |
+ root_window_->AddPreTargetHandler(this); |
+} |
+ |
+TouchAccessibilityEnabler::~TouchAccessibilityEnabler() { |
+ root_window_->RemovePreTargetHandler(this); |
+} |
+ |
+void TouchAccessibilityEnabler::OnTouchEvent(ui::TouchEvent* event) { |
+ // Skip events rewritten by TouchExplorationController, it will hand |
+ // us the unrewritten events directly. |
+ if (!(event->flags() & ui::EF_TOUCH_ACCESSIBILITY)) |
+ HandleTouchEvent(*event); |
+} |
+ |
+void TouchAccessibilityEnabler::HandleTouchEvent(const ui::TouchEvent& event) { |
+ DCHECK(!(event.flags() & ui::EF_TOUCH_ACCESSIBILITY)); |
+ const ui::EventType type = event.type(); |
+ const gfx::PointF& location = event.location_f(); |
+ const int touch_id = event.touch_id(); |
+ |
+ if (type == ui::ET_TOUCH_PRESSED) { |
+ touch_locations_.insert(std::pair<int, gfx::PointF>(touch_id, location)); |
+ } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { |
+ auto iter = touch_locations_.find(touch_id); |
+ |
+ // Can happen if this object is constructed while fingers were down. |
+ if (iter == touch_locations_.end()) |
+ return; |
+ |
+ touch_locations_.erase(touch_id); |
+ } else if (type == ui::ET_TOUCH_MOVED) { |
+ auto iter = touch_locations_.find(touch_id); |
+ |
+ // Can happen if this object is constructed while fingers were down. |
+ if (iter == touch_locations_.end()) |
+ return; |
+ |
+ float delta = (location - iter->second).Length(); |
+ if (delta > gesture_detector_config_.double_tap_slop) { |
+ state_ = WAIT_FOR_NO_FINGERS; |
+ CancelTimer(); |
+ return; |
+ } |
+ } else { |
+ NOTREACHED() << "Unexpected event type received: " << event.name(); |
+ return; |
+ } |
+ |
+ if (touch_locations_.size() == 0) { |
+ state_ = NO_FINGERS_DOWN; |
+ CancelTimer(); |
+ return; |
+ } |
+ |
+ if (touch_locations_.size() > 2) { |
+ state_ = WAIT_FOR_NO_FINGERS; |
+ CancelTimer(); |
+ return; |
+ } |
+ |
+ if (state_ == NO_FINGERS_DOWN && event.type() == ui::ET_TOUCH_PRESSED) { |
+ state_ = ONE_FINGER_DOWN; |
+ } else if (state_ == ONE_FINGER_DOWN && |
+ event.type() == ui::ET_TOUCH_PRESSED) { |
+ state_ = TWO_FINGERS_DOWN; |
+ two_finger_start_time_ = Now(); |
+ StartTimer(); |
+ } |
+} |
+ |
+base::TimeTicks TouchAccessibilityEnabler::Now() { |
+ if (tick_clock_) { |
+ // This is the same as what EventTimeForNow() does, but here we do it |
+ // with a clock that can be replaced with a simulated clock for tests. |
+ return tick_clock_->NowTicks(); |
+ } |
+ return ui::EventTimeForNow(); |
+} |
+ |
+void TouchAccessibilityEnabler::StartTimer() { |
+ if (timer_.IsRunning()) |
+ return; |
+ |
+ timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kTimerDelayInMS), |
+ this, &ui::TouchAccessibilityEnabler::OnTimer); |
+} |
+ |
+void TouchAccessibilityEnabler::CancelTimer() { |
+ if (timer_.IsRunning()) |
+ timer_.Stop(); |
+} |
+ |
+void TouchAccessibilityEnabler::OnTimer() { |
+ base::TimeTicks now = Now(); |
+ double tick_count_f = |
+ (now - two_finger_start_time_).InMillisecondsF() / kTimerDelayInMS; |
+ int tick_count = roundf(tick_count_f); |
+ |
+ if (tick_count == kTimerTicksOfFirstSoundFeedback) { |
+ base::RecordAction( |
+ base::UserMetricsAction("Accessibility.TwoFingersHeldDown")); |
+ } |
+ |
+ if (tick_count >= kTimerTicksOfFirstSoundFeedback && |
+ tick_count < kTimerTicksToToggleSpokenFeedback) { |
+ delegate_->PlaySpokenFeedbackToggleCountdown(tick_count); |
+ } |
+ if (tick_count == kTimerTicksToToggleSpokenFeedback) { |
+ delegate_->ToggleSpokenFeedback(); |
+ state_ = WAIT_FOR_NO_FINGERS; |
+ } |
+} |
+ |
+} // namespace ui |