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