blob: 04653dc36a3828e0c9db0e5f3837d2f992a316f1 [file] [log] [blame]
// Copyright (c) 2013 The Chromium OS 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 "gestures/include/metrics_filter_interpreter.h"
#include <cmath>
#include "gestures/include/filter_interpreter.h"
#include "gestures/include/finger_metrics.h"
#include "gestures/include/gestures.h"
#include "gestures/include/logging.h"
#include "gestures/include/prop_registry.h"
#include "gestures/include/tracer.h"
#include "gestures/include/util.h"
namespace gestures {
MetricsFilterInterpreter::MetricsFilterInterpreter(
PropRegistry* prop_reg,
Interpreter* next,
Tracer* tracer,
GestureInterpreterDeviceClass devclass)
: FilterInterpreter(NULL, next, tracer, false),
mstate_mm_(kMaxFingers * MState::MaxHistorySize()),
history_mm_(kMaxFingers),
devclass_(devclass),
mouse_movement_session_index_(0),
mouse_movement_current_session_length(0),
mouse_movement_current_session_start(0),
mouse_movement_current_session_last(0),
mouse_movement_current_session_distance(0),
noisy_ground_distance_threshold_(prop_reg,
"Metrics Noisy Ground Distance",
10.0),
noisy_ground_time_threshold_(prop_reg, "Metrics Noisy Ground Time", 0.1),
mouse_moving_time_threshold_(prop_reg,
"Metrics Mouse Moving Time",
0.05),
mouse_control_warmup_sessions_(prop_reg,
"Metrics Mouse Warmup Session",
100) {
InitName();
}
void MetricsFilterInterpreter::SyncInterpretImpl(HardwareState* hwstate,
stime_t* timeout) {
if (devclass_ == GESTURES_DEVCLASS_TOUCHPAD) {
// Right now, we only want to update finger states for built-in touchpads
// because all the generated metrics gestures would be put under each
// platform's hat on the Chrome UMA dashboard. If we send metrics gestures
// (e.g. noisy ground instances) for external peripherals (e.g. multi-touch
// mice), they would be mistaken as from the platform's touchpad and thus
// results in over-counting.
//
// TODO(sheckylin): Don't send metric gestures for external touchpads
// either.
// TODO(sheckylin): Track finger related metrics for external peripherals
// as well after gaining access to the UMA log.
UpdateFingerState(*hwstate);
} else if (devclass_ == GESTURES_DEVCLASS_MOUSE ||
devclass_ == GESTURES_DEVCLASS_MULTITOUCH_MOUSE ||
devclass_ == GESTURES_DEVCLASS_POINTING_STICK) {
UpdateMouseMovementState(*hwstate);
}
next_->SyncInterpret(hwstate, timeout);
}
template <class StateType, class DataType>
void MetricsFilterInterpreter::AddNewStateToBuffer(
MemoryManagedList<StateType>* history,
const DataType& data,
const HardwareState& hwstate) {
// The history buffer is already full, pop one
if (history->size() == StateType::MaxHistorySize())
history->DeleteFront();
// Push the new finger state to the back of buffer
StateType* current = history->PushNewEltBack();
if (!current) {
Err("MetricsFilterInterpreter buffer out of space");
return;
}
current->Init(data, hwstate);
}
void MetricsFilterInterpreter::UpdateMouseMovementState(
const HardwareState& hwstate) {
// Skip finger-only hardware states for multi-touch mice.
if (hwstate.rel_x == 0 && hwstate.rel_y == 0)
return;
// If the last movement is too long ago, we consider the history
// an independent session. Report statistic for it and start a new
// one.
if (mouse_movement_current_session_length >= 1 &&
(hwstate.timestamp - mouse_movement_current_session_last >
mouse_moving_time_threshold_.val_)) {
// We skip the first a few sessions right after the user starts using the
// mouse because they tend to be more noisy.
if (mouse_movement_session_index_ >= mouse_control_warmup_sessions_.val_)
ReportMouseStatistics();
mouse_movement_current_session_length = 0;
mouse_movement_current_session_distance = 0;
++mouse_movement_session_index_;
}
// We skip the movement of the first event because there is no way to tell
// the start time of it.
if (!mouse_movement_current_session_length) {
mouse_movement_current_session_start = hwstate.timestamp;
} else {
mouse_movement_current_session_distance +=
sqrtf(hwstate.rel_x * hwstate.rel_x + hwstate.rel_y * hwstate.rel_y);
}
mouse_movement_current_session_last = hwstate.timestamp;
++mouse_movement_current_session_length;
}
void MetricsFilterInterpreter::ReportMouseStatistics() {
// At least 2 samples are needed to compute delta t.
if (mouse_movement_current_session_length == 1)
return;
// Compute the average speed.
stime_t session_time = mouse_movement_current_session_last -
mouse_movement_current_session_start;
double avg_speed = mouse_movement_current_session_distance / session_time;
// Send the metrics gesture.
ProduceGesture(Gesture(kGestureMetrics,
mouse_movement_current_session_start,
mouse_movement_current_session_last,
kGestureMetricsTypeMouseMovement,
avg_speed,
session_time));
}
void MetricsFilterInterpreter::UpdateFingerState(
const HardwareState& hwstate) {
FingerHistoryMap removed;
RemoveMissingIdsFromMap(&histories_, hwstate, &removed);
for (FingerHistoryMap::const_iterator it =
removed.begin(); it != removed.end(); ++it) {
it->second->DeleteAll();
history_mm_.Free(it->second);
}
FingerState *fs = hwstate.fingers;
for (short i = 0; i < hwstate.finger_cnt; i++) {
FingerHistory* hp;
// Update the map if the contact is new
if (!MapContainsKey(histories_, fs[i].tracking_id)) {
hp = history_mm_.Allocate();
if (!hp) {
Err("FingerHistory out of space");
continue;
}
hp->Init(&mstate_mm_);
histories_[fs[i].tracking_id] = hp;
} else {
hp = histories_[fs[i].tracking_id];
}
// Check if the finger history contains interesting patterns
AddNewStateToBuffer(hp, fs[i], hwstate);
DetectNoisyGround(hp);
}
}
bool MetricsFilterInterpreter::DetectNoisyGround(
const FingerHistory* history) {
MState* current = history->Tail();
size_t n_samples = history->size();
// Noise pattern takes 3 samples
if (n_samples < 3)
return false;
MState* past_1 = current->prev_;
MState* past_2 = past_1->prev_;
// Noise pattern needs to happen in a short period of time
if(current->timestamp - past_2->timestamp >
noisy_ground_time_threshold_.val_) {
return false;
}
// vec[when][x,y]
float vec[2][2];
vec[0][0] = current->data.position_x - past_1->data.position_x;
vec[0][1] = current->data.position_y - past_1->data.position_y;
vec[1][0] = past_1->data.position_x - past_2->data.position_x;
vec[1][1] = past_1->data.position_y - past_2->data.position_y;
const float thr = noisy_ground_distance_threshold_.val_;
// We dictate the noise pattern as two consecutive big moves in
// opposite directions in either X or Y
for (size_t i = 0; i < arraysize(vec[0]); i++)
if ((vec[0][i] < -thr && vec[1][i] > thr) ||
(vec[0][i] > thr && vec[1][i] < -thr)) {
ProduceGesture(Gesture(kGestureMetrics, past_2->timestamp,
current->timestamp, kGestureMetricsTypeNoisyGround,
vec[0][i], vec[1][i]));
return true;
}
return false;
}
} // namespace gestures