1// Copyright 2013 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
5#include "ash/touch/touch_hud_debug.h"
6
7#include "ash/display/display_manager.h"
8#include "ash/root_window_controller.h"
9#include "ash/shell.h"
10#include "base/json/json_string_value_serializer.h"
11#include "base/strings/string_number_conversions.h"
12#include "base/strings/stringprintf.h"
13#include "base/strings/utf_string_conversions.h"
14#include "third_party/skia/include/core/SkPath.h"
15#include "ui/aura/window_event_dispatcher.h"
16#include "ui/events/event.h"
17#include "ui/gfx/animation/animation_delegate.h"
18#include "ui/gfx/canvas.h"
19#include "ui/gfx/display.h"
20#include "ui/gfx/size.h"
21#include "ui/gfx/transform.h"
22#include "ui/views/controls/label.h"
23#include "ui/views/layout/box_layout.h"
24#include "ui/views/widget/widget.h"
25
26#if defined(USE_X11)
27#include <X11/extensions/XInput2.h>
28#include <X11/Xlib.h>
29
30#include "ui/events/x/device_data_manager_x11.h"
31#endif
32
33namespace ash {
34
35const int kPointRadius = 20;
36const SkColor kColors[] = {
37  SK_ColorYELLOW,
38  SK_ColorGREEN,
39  SK_ColorRED,
40  SK_ColorBLUE,
41  SK_ColorGRAY,
42  SK_ColorMAGENTA,
43  SK_ColorCYAN,
44  SK_ColorWHITE,
45  SK_ColorBLACK,
46  SkColorSetRGB(0xFF, 0x8C, 0x00),
47  SkColorSetRGB(0x8B, 0x45, 0x13),
48  SkColorSetRGB(0xFF, 0xDE, 0xAD),
49};
50const int kAlpha = 0x60;
51const int kMaxPaths = arraysize(kColors);
52const int kReducedScale = 10;
53
54const char* GetTouchEventLabel(ui::EventType type) {
55  switch (type) {
56    case ui::ET_UNKNOWN:
57      return " ";
58    case ui::ET_TOUCH_PRESSED:
59      return "P";
60    case ui::ET_TOUCH_MOVED:
61      return "M";
62    case ui::ET_TOUCH_RELEASED:
63      return "R";
64    case ui::ET_TOUCH_CANCELLED:
65      return "C";
66    default:
67      break;
68  }
69  return "?";
70}
71
72int GetTrackingId(const ui::TouchEvent& event) {
73  if (!event.HasNativeEvent())
74    return 0;
75#if defined(USE_XI2_MT)
76  ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
77  double tracking_id;
78  if (manager->GetEventData(*event.native_event(),
79                            ui::DeviceDataManagerX11::DT_TOUCH_TRACKING_ID,
80                            &tracking_id)) {
81    return static_cast<int>(tracking_id);
82  }
83#endif
84  return 0;
85}
86
87int GetSourceDeviceId(const ui::TouchEvent& event) {
88  if (!event.HasNativeEvent())
89    return 0;
90#if defined(USE_X11)
91  XEvent* xev = event.native_event();
92  return static_cast<XIDeviceEvent*>(xev->xcookie.data)->sourceid;
93#endif
94  return 0;
95}
96
97// A TouchPointLog represents a single touch-event of a touch point.
98struct TouchPointLog {
99 public:
100  explicit TouchPointLog(const ui::TouchEvent& touch)
101      : id(touch.touch_id()),
102        type(touch.type()),
103        location(touch.root_location()),
104        timestamp(touch.time_stamp().InMillisecondsF()),
105        radius_x(touch.radius_x()),
106        radius_y(touch.radius_y()),
107        pressure(touch.force()),
108        tracking_id(GetTrackingId(touch)),
109        source_device(GetSourceDeviceId(touch)) {
110  }
111
112  // Populates a dictionary value with all the information about the touch
113  // point.
114  scoped_ptr<base::DictionaryValue> GetAsDictionary() const {
115    scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
116
117    value->SetInteger("id", id);
118    value->SetString("type", std::string(GetTouchEventLabel(type)));
119    value->SetString("location", location.ToString());
120    value->SetDouble("timestamp", timestamp);
121    value->SetDouble("radius_x", radius_x);
122    value->SetDouble("radius_y", radius_y);
123    value->SetDouble("pressure", pressure);
124    value->SetInteger("tracking_id", tracking_id);
125    value->SetInteger("source_device", source_device);
126
127    return value.Pass();
128  }
129
130  int id;
131  ui::EventType type;
132  gfx::Point location;
133  double timestamp;
134  float radius_x;
135  float radius_y;
136  float pressure;
137  int tracking_id;
138  int source_device;
139};
140
141// A TouchTrace keeps track of all the touch events of a single touch point
142// (starting from a touch-press and ending at a touch-release or touch-cancel).
143class TouchTrace {
144 public:
145  typedef std::vector<TouchPointLog>::iterator iterator;
146  typedef std::vector<TouchPointLog>::const_iterator const_iterator;
147  typedef std::vector<TouchPointLog>::reverse_iterator reverse_iterator;
148  typedef std::vector<TouchPointLog>::const_reverse_iterator
149      const_reverse_iterator;
150
151  TouchTrace() {
152  }
153
154  void AddTouchPoint(const ui::TouchEvent& touch) {
155    log_.push_back(TouchPointLog(touch));
156  }
157
158  const std::vector<TouchPointLog>& log() const { return log_; }
159
160  bool active() const {
161    return !log_.empty() && log_.back().type != ui::ET_TOUCH_RELEASED &&
162        log_.back().type != ui::ET_TOUCH_CANCELLED;
163  }
164
165  // Returns a list containing data from all events for the touch point.
166  scoped_ptr<base::ListValue> GetAsList() const {
167    scoped_ptr<base::ListValue> list(new base::ListValue());
168    for (const_iterator i = log_.begin(); i != log_.end(); ++i)
169      list->Append((*i).GetAsDictionary().release());
170    return list.Pass();
171  }
172
173  void Reset() {
174    log_.clear();
175  }
176
177 private:
178  std::vector<TouchPointLog> log_;
179
180  DISALLOW_COPY_AND_ASSIGN(TouchTrace);
181};
182
183// A TouchLog keeps track of all touch events of all touch points.
184class TouchLog {
185 public:
186  TouchLog() : next_trace_index_(0) {
187  }
188
189  void AddTouchPoint(const ui::TouchEvent& touch) {
190    if (touch.type() == ui::ET_TOUCH_PRESSED)
191      StartTrace(touch);
192    AddToTrace(touch);
193  }
194
195  void Reset() {
196    next_trace_index_ = 0;
197    for (int i = 0; i < kMaxPaths; ++i)
198      traces_[i].Reset();
199  }
200
201  scoped_ptr<base::ListValue> GetAsList() const {
202    scoped_ptr<base::ListValue> list(new base::ListValue());
203    for (int i = 0; i < kMaxPaths; ++i) {
204      if (!traces_[i].log().empty())
205        list->Append(traces_[i].GetAsList().release());
206    }
207    return list.Pass();
208  }
209
210  int GetTraceIndex(int touch_id) const {
211    return touch_id_to_trace_index_.at(touch_id);
212  }
213
214  const TouchTrace* traces() const {
215    return traces_;
216  }
217
218 private:
219  void StartTrace(const ui::TouchEvent& touch) {
220    // Find the first inactive spot; otherwise, overwrite the one
221    // |next_trace_index_| is pointing to.
222    int old_trace_index = next_trace_index_;
223    do {
224      if (!traces_[next_trace_index_].active())
225        break;
226      next_trace_index_ = (next_trace_index_ + 1) % kMaxPaths;
227    } while (next_trace_index_ != old_trace_index);
228    int touch_id = touch.touch_id();
229    traces_[next_trace_index_].Reset();
230    touch_id_to_trace_index_[touch_id] = next_trace_index_;
231    next_trace_index_ = (next_trace_index_ + 1) % kMaxPaths;
232  }
233
234  void AddToTrace(const ui::TouchEvent& touch) {
235    int touch_id = touch.touch_id();
236    int trace_index = touch_id_to_trace_index_[touch_id];
237    traces_[trace_index].AddTouchPoint(touch);
238  }
239
240  TouchTrace traces_[kMaxPaths];
241  int next_trace_index_;
242
243  std::map<int, int> touch_id_to_trace_index_;
244
245  DISALLOW_COPY_AND_ASSIGN(TouchLog);
246};
247
248// TouchHudCanvas draws touch traces in |FULLSCREEN| and |REDUCED_SCALE| modes.
249class TouchHudCanvas : public views::View {
250 public:
251  explicit TouchHudCanvas(const TouchLog& touch_log)
252      : touch_log_(touch_log),
253        scale_(1) {
254    SetPaintToLayer(true);
255    SetFillsBoundsOpaquely(false);
256
257    paint_.setStyle(SkPaint::kFill_Style);
258  }
259
260  virtual ~TouchHudCanvas() {}
261
262  void SetScale(int scale) {
263    if (scale_ == scale)
264      return;
265    scale_ = scale;
266    gfx::Transform transform;
267    transform.Scale(1. / scale_, 1. / scale_);
268    layer()->SetTransform(transform);
269  }
270
271  int scale() const { return scale_; }
272
273  void TouchPointAdded(int touch_id) {
274    int trace_index = touch_log_.GetTraceIndex(touch_id);
275    const TouchTrace& trace = touch_log_.traces()[trace_index];
276    const TouchPointLog& point = trace.log().back();
277    if (point.type == ui::ET_TOUCH_PRESSED)
278      StartedTrace(trace_index);
279    if (point.type != ui::ET_TOUCH_CANCELLED)
280      AddedPointToTrace(trace_index);
281  }
282
283  void Clear() {
284    for (int i = 0; i < kMaxPaths; ++i)
285      paths_[i].reset();
286
287    SchedulePaint();
288  }
289
290 private:
291  void StartedTrace(int trace_index) {
292    paths_[trace_index].reset();
293    colors_[trace_index] = SkColorSetA(kColors[trace_index], kAlpha);
294  }
295
296  void AddedPointToTrace(int trace_index) {
297    const TouchTrace& trace = touch_log_.traces()[trace_index];
298    const TouchPointLog& point = trace.log().back();
299    const gfx::Point& location = point.location;
300    SkScalar x = SkIntToScalar(location.x());
301    SkScalar y = SkIntToScalar(location.y());
302    SkPoint last;
303    if (!paths_[trace_index].getLastPt(&last) || x != last.x() ||
304        y != last.y()) {
305      paths_[trace_index].addCircle(x, y, SkIntToScalar(kPointRadius));
306      SchedulePaint();
307    }
308  }
309
310  // Overridden from views::View.
311  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
312    for (int i = 0; i < kMaxPaths; ++i) {
313      if (paths_[i].countPoints() == 0)
314        continue;
315      paint_.setColor(colors_[i]);
316      canvas->DrawPath(paths_[i], paint_);
317    }
318  }
319
320  SkPaint paint_;
321
322  const TouchLog& touch_log_;
323  SkPath paths_[kMaxPaths];
324  SkColor colors_[kMaxPaths];
325
326  int scale_;
327
328  DISALLOW_COPY_AND_ASSIGN(TouchHudCanvas);
329};
330
331TouchHudDebug::TouchHudDebug(aura::Window* initial_root)
332    : TouchObserverHUD(initial_root),
333      mode_(FULLSCREEN),
334      touch_log_(new TouchLog()),
335      canvas_(NULL),
336      label_container_(NULL) {
337  const gfx::Display& display =
338      Shell::GetInstance()->display_manager()->GetDisplayForId(display_id());
339
340  views::View* content = widget()->GetContentsView();
341
342  canvas_ = new TouchHudCanvas(*touch_log_);
343  content->AddChildView(canvas_);
344
345  const gfx::Size& display_size = display.size();
346  canvas_->SetSize(display_size);
347
348  label_container_ = new views::View;
349  label_container_->SetLayoutManager(new views::BoxLayout(
350      views::BoxLayout::kVertical, 0, 0, 0));
351
352  for (int i = 0; i < kMaxTouchPoints; ++i) {
353    touch_labels_[i] = new views::Label;
354    touch_labels_[i]->SetBackgroundColor(SkColorSetARGB(0, 255, 255, 255));
355    touch_labels_[i]->SetShadows(gfx::ShadowValues(
356        1, gfx::ShadowValue(gfx::Point(1, 1), 0, SK_ColorWHITE)));
357    label_container_->AddChildView(touch_labels_[i]);
358  }
359  label_container_->SetX(0);
360  label_container_->SetY(display_size.height() / kReducedScale);
361  label_container_->SetSize(label_container_->GetPreferredSize());
362  label_container_->SetVisible(false);
363  content->AddChildView(label_container_);
364}
365
366TouchHudDebug::~TouchHudDebug() {
367}
368
369// static
370scoped_ptr<base::DictionaryValue> TouchHudDebug::GetAllAsDictionary() {
371  scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
372  aura::Window::Windows roots = Shell::GetInstance()->GetAllRootWindows();
373  for (aura::Window::Windows::iterator iter = roots.begin();
374      iter != roots.end(); ++iter) {
375    RootWindowController* controller = GetRootWindowController(*iter);
376    TouchHudDebug* hud = controller->touch_hud_debug();
377    if (hud) {
378      scoped_ptr<base::ListValue> list = hud->GetLogAsList();
379      if (!list->empty())
380        value->Set(base::Int64ToString(hud->display_id()), list.release());
381    }
382  }
383  return value.Pass();
384}
385
386void TouchHudDebug::ChangeToNextMode() {
387  switch (mode_) {
388    case FULLSCREEN:
389      SetMode(REDUCED_SCALE);
390      break;
391    case REDUCED_SCALE:
392      SetMode(INVISIBLE);
393      break;
394    case INVISIBLE:
395      SetMode(FULLSCREEN);
396      break;
397  }
398}
399
400scoped_ptr<base::ListValue> TouchHudDebug::GetLogAsList() const {
401  return touch_log_->GetAsList();
402}
403
404void TouchHudDebug::Clear() {
405  if (widget()->IsVisible()) {
406    canvas_->Clear();
407    for (int i = 0; i < kMaxTouchPoints; ++i)
408      touch_labels_[i]->SetText(base::string16());
409    label_container_->SetSize(label_container_->GetPreferredSize());
410  }
411}
412
413void TouchHudDebug::SetMode(Mode mode) {
414  if (mode_ == mode)
415    return;
416  mode_ = mode;
417  switch (mode) {
418    case FULLSCREEN:
419      label_container_->SetVisible(false);
420      canvas_->SetVisible(true);
421      canvas_->SetScale(1);
422      canvas_->SchedulePaint();
423      widget()->Show();
424      break;
425    case REDUCED_SCALE:
426      label_container_->SetVisible(true);
427      canvas_->SetVisible(true);
428      canvas_->SetScale(kReducedScale);
429      canvas_->SchedulePaint();
430      widget()->Show();
431      break;
432    case INVISIBLE:
433      widget()->Hide();
434      break;
435  }
436}
437
438void TouchHudDebug::UpdateTouchPointLabel(int index) {
439  int trace_index = touch_log_->GetTraceIndex(index);
440  const TouchTrace& trace = touch_log_->traces()[trace_index];
441  TouchTrace::const_reverse_iterator point = trace.log().rbegin();
442  ui::EventType touch_status = point->type;
443  float touch_radius = std::max(point->radius_x, point->radius_y);
444  while (point != trace.log().rend() && point->type == ui::ET_TOUCH_CANCELLED)
445    point++;
446  DCHECK(point != trace.log().rend());
447  gfx::Point touch_position = point->location;
448
449  std::string string = base::StringPrintf("%2d: %s %s (%.4f)",
450                                          index,
451                                          GetTouchEventLabel(touch_status),
452                                          touch_position.ToString().c_str(),
453                                          touch_radius);
454  touch_labels_[index]->SetText(base::UTF8ToUTF16(string));
455}
456
457void TouchHudDebug::OnTouchEvent(ui::TouchEvent* event) {
458  if (event->touch_id() >= kMaxTouchPoints)
459    return;
460
461  touch_log_->AddTouchPoint(*event);
462  canvas_->TouchPointAdded(event->touch_id());
463  UpdateTouchPointLabel(event->touch_id());
464  label_container_->SetSize(label_container_->GetPreferredSize());
465}
466
467void TouchHudDebug::OnDisplayMetricsChanged(const gfx::Display& display,
468                                            uint32_t metrics) {
469  TouchObserverHUD::OnDisplayMetricsChanged(display, metrics);
470
471  if (display.id() != display_id() || !(metrics & DISPLAY_METRIC_BOUNDS))
472    return;
473  const gfx::Size& size = display.size();
474  canvas_->SetSize(size);
475  label_container_->SetY(size.height() / kReducedScale);
476}
477
478void TouchHudDebug::SetHudForRootWindowController(
479    RootWindowController* controller) {
480  controller->set_touch_hud_debug(this);
481}
482
483void TouchHudDebug::UnsetHudForRootWindowController(
484    RootWindowController* controller) {
485  controller->set_touch_hud_debug(NULL);
486}
487
488}  // namespace ash
489