blob: e9f8cca7cd77370cd5ce8241587bc5b6af74f094 [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 "ash/accessibility/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 ash {
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_(nullptr),
weak_factory_(this) {
DCHECK(root_window);
DCHECK(delegate);
AddEventHandler();
}
TouchAccessibilityEnabler::~TouchAccessibilityEnabler() {
RemoveEventHandler();
}
void TouchAccessibilityEnabler::RemoveEventHandler() {
if (event_handler_installed_) {
root_window_->RemovePreTargetHandler(this);
event_handler_installed_ = false;
ResetToNoFingersDown();
}
}
void TouchAccessibilityEnabler::AddEventHandler() {
if (!event_handler_installed_) {
root_window_->AddPreTargetHandler(this);
event_handler_installed_ = true;
ResetToNoFingersDown();
}
}
void TouchAccessibilityEnabler::OnTouchEvent(ui::TouchEvent* event) {
DCHECK(!(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.pointer_details().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.GetName();
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();
delegate_->OnTwoFingerTouchStart();
}
}
base::WeakPtr<TouchAccessibilityEnabler>
TouchAccessibilityEnabler::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
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, &TouchAccessibilityEnabler::OnTimer);
}
void TouchAccessibilityEnabler::CancelTimer() {
if (timer_.IsRunning()) {
timer_.Stop();
delegate_->OnTwoFingerTouchStop();
}
}
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;
}
}
void TouchAccessibilityEnabler::ResetToNoFingersDown() {
state_ = NO_FINGERS_DOWN;
touch_locations_.clear();
CancelTimer();
}
} // namespace ash