blob: 675f396b08ec9b87654a3dda06eb868bc62a2412 [file] [log] [blame]
// 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/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 toggling spoken feedback.
constexpr int kTimerTicksToToggleSpokenFeedback = 7;
} // 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 >= 1 && tick_count < kTimerTicksToToggleSpokenFeedback) {
delegate_->PlaySpokenFeedbackToggleCountdown(tick_count);
}
if (tick_count == kTimerTicksToToggleSpokenFeedback) {
delegate_->ToggleSpokenFeedback();
state_ = WAIT_FOR_NO_FINGERS;
}
}
} // namespace ui