1 // Copyright 2016 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/display/touch_calibrator_controller.h"
6 
7 #include <algorithm>
8 #include <memory>
9 
10 #include "ash/display/touch_calibrator_view.h"
11 #include "ash/display/window_tree_host_manager.h"
12 #include "ash/host/ash_window_tree_host.h"
13 #include "ash/shell.h"
14 #include "ash/touch/ash_touch_transform_controller.h"
15 #include "base/bind.h"
16 #include "base/threading/thread_task_runner_handle.h"
17 #include "ui/aura/window_tree_host.h"
18 #include "ui/display/manager/touch_device_manager.h"
19 #include "ui/display/screen.h"
20 #include "ui/events/devices/device_data_manager.h"
21 #include "ui/events/event.h"
22 #include "ui/events/types/event_type.h"
23 #include "ui/gfx/geometry/size_conversions.h"
24 #include "ui/views/widget/widget.h"
25 
26 namespace ash {
27 namespace {
28 
InitInternalTouchDeviceIds(std::set<int> & internal_touch_device_ids)29 void InitInternalTouchDeviceIds(std::set<int>& internal_touch_device_ids) {
30   internal_touch_device_ids.clear();
31   const std::vector<ui::TouchscreenDevice>& device_list =
32       ui::DeviceDataManager::GetInstance()->GetTouchscreenDevices();
33   for (const auto& touchscreen_device : device_list) {
34     if (touchscreen_device.type == ui::InputDeviceType::INPUT_DEVICE_INTERNAL)
35       internal_touch_device_ids.insert(touchscreen_device.id);
36   }
37 }
38 
39 // Returns a transform to undo any transformations that are applied to events
40 // originating from the touch device identified with |touch_device_id|. This
41 // transform converts the event's location to the raw touch location.
CalculateEventTransformer(int touch_device_id)42 gfx::Transform CalculateEventTransformer(int touch_device_id) {
43   const display::DisplayManager* display_manager =
44       Shell::Get()->display_manager();
45   const std::vector<ui::TouchscreenDevice>& device_list =
46       ui::DeviceDataManager::GetInstance()->GetTouchscreenDevices();
47 
48   auto device_it = std::find_if(
49       device_list.begin(), device_list.end(),
50       [&](const auto& device) { return device.id == touch_device_id; });
51   DCHECK(device_it != device_list.end())
52       << "Device id " << touch_device_id
53       << " is invalid. No such device connected to system";
54 
55   int64_t previous_display_id =
56       display_manager->touch_device_manager()->GetAssociatedDisplay(*device_it);
57 
58   // If the touch device is not associated with any display. This may happen in
59   // tests when the test does not setup the |ui::TouchDeviceTransform| before
60   // generating a touch event.
61   if (previous_display_id == display::kInvalidDisplayId)
62     return gfx::Transform();
63 
64   // Undo the event transformations that the previous display applied on the
65   // event location. We want to store the raw event location information.
66   gfx::Transform tm =
67       Shell::Get()
68           ->window_tree_host_manager()
69           ->GetAshWindowTreeHostForDisplayId(previous_display_id)
70           ->AsWindowTreeHost()
71           ->GetRootTransform();
72   return tm;
73 }
74 
75 }  // namespace
76 
77 // Time interval after a touch event during which all other touch events are
78 // ignored during calibration.
79 const base::TimeDelta TouchCalibratorController::kTouchIntervalThreshold =
80     base::TimeDelta::FromMilliseconds(200);
81 
TouchCalibratorController()82 TouchCalibratorController::TouchCalibratorController()
83     : last_touch_timestamp_(base::Time::Now()) {}
84 
~TouchCalibratorController()85 TouchCalibratorController::~TouchCalibratorController() {
86   touch_calibrator_widgets_.clear();
87   StopCalibrationAndResetParams();
88 }
89 
OnDisplayConfigurationChanged()90 void TouchCalibratorController::OnDisplayConfigurationChanged() {
91   touch_calibrator_widgets_.clear();
92   StopCalibrationAndResetParams();
93 }
94 
StartCalibration(const display::Display & target_display,bool is_custom_calibration,TouchCalibrationCallback opt_callback)95 void TouchCalibratorController::StartCalibration(
96     const display::Display& target_display,
97     bool is_custom_calibration,
98     TouchCalibrationCallback opt_callback) {
99   state_ = is_custom_calibration ? CalibrationState::kCustomCalibration
100                                  : CalibrationState::kNativeCalibration;
101 
102   if (opt_callback)
103     opt_callback_ = std::move(opt_callback);
104 
105   target_display_ = target_display;
106 
107   // Clear all touch calibrator views used in any previous calibration.
108   touch_calibrator_widgets_.clear();
109 
110   // Set the touch device id as invalid so it can be set during calibration.
111   touch_device_id_ = ui::InputDevice::kInvalidId;
112 
113   // Populate |internal_touch_device_ids_| with the ids of touch devices that
114   // are currently associated with the internal display and are of type
115   // |ui::InputDeviceType::INPUT_DEVICE_INTERNAL|.
116   InitInternalTouchDeviceIds(internal_touch_device_ids_);
117 
118   // If this is a native touch calibration, then initialize the UX for it.
119   if (state_ == CalibrationState::kNativeCalibration) {
120     Shell::Get()->window_tree_host_manager()->AddObserver(this);
121 
122     // Reset the calibration data.
123     touch_point_quad_.fill(std::make_pair(gfx::Point(0, 0), gfx::Point(0, 0)));
124 
125     std::vector<display::Display> displays =
126         display::Screen::GetScreen()->GetAllDisplays();
127 
128     for (const display::Display& display : displays) {
129       bool is_primary_view = display.id() == target_display_.id();
130       touch_calibrator_widgets_[display.id()] =
131           TouchCalibratorView::Create(display, is_primary_view);
132     }
133   }
134 
135   Shell::Get()->touch_transformer_controller()->SetForCalibration(true);
136 
137   // Add self as an event handler target.
138   Shell::Get()->AddPreTargetHandler(this);
139 }
140 
StopCalibrationAndResetParams()141 void TouchCalibratorController::StopCalibrationAndResetParams() {
142   if (!IsCalibrating())
143     return;
144   Shell::Get()->window_tree_host_manager()->RemoveObserver(this);
145 
146   Shell::Get()->touch_transformer_controller()->SetForCalibration(false);
147 
148   // Remove self as the event handler.
149   Shell::Get()->RemovePreTargetHandler(this);
150 
151   // Transition all touch calibrator views to their final state for a graceful
152   // exit if this is touch calibration with native UX.
153   if (state_ == CalibrationState::kNativeCalibration) {
154     for (const auto& it : touch_calibrator_widgets_)
155       static_cast<TouchCalibratorView*>(it.second->GetContentsView())
156           ->SkipToFinalState();
157   }
158 
159   state_ = CalibrationState::kInactive;
160 
161   if (opt_callback_) {
162     base::ThreadTaskRunnerHandle::Get()->PostTask(
163         FROM_HERE,
164         base::BindOnce(std::move(opt_callback_), false /* failure */));
165     opt_callback_.Reset();
166   }
167 }
168 
CompleteCalibration(const CalibrationPointPairQuad & pairs,const gfx::Size & display_size)169 void TouchCalibratorController::CompleteCalibration(
170     const CalibrationPointPairQuad& pairs,
171     const gfx::Size& display_size) {
172   bool did_find_touch_device = false;
173   const std::vector<ui::TouchscreenDevice>& device_list =
174       ui::DeviceDataManager::GetInstance()->GetTouchscreenDevices();
175   ui::TouchscreenDevice target_device;
176   for (const auto& device : device_list) {
177     if (device.id == touch_device_id_) {
178       target_device = device;
179       did_find_touch_device = true;
180       break;
181     }
182   }
183 
184   if (!did_find_touch_device) {
185     VLOG(1) << "No touch device with id: " << touch_device_id_ << " found to "
186             << "complete touch calibration for display with id: "
187             << target_display_.id() << ". Storing it as a fallback";
188   }
189 
190   if (opt_callback_) {
191     base::ThreadTaskRunnerHandle::Get()->PostTask(
192         FROM_HERE,
193         base::BindOnce(std::move(opt_callback_), true /* success */));
194     opt_callback_.Reset();
195   }
196   StopCalibrationAndResetParams();
197   Shell::Get()->display_manager()->SetTouchCalibrationData(
198       target_display_.id(), pairs, display_size, target_device);
199 }
200 
IsCalibrating() const201 bool TouchCalibratorController::IsCalibrating() const {
202   return state_ != CalibrationState::kInactive;
203 }
204 
205 // ui::EventHandler:
OnKeyEvent(ui::KeyEvent * key)206 void TouchCalibratorController::OnKeyEvent(ui::KeyEvent* key) {
207   if (state_ != CalibrationState::kNativeCalibration)
208     return;
209   // Detect ESC key press.
210   if (key->type() == ui::ET_KEY_PRESSED && key->key_code() == ui::VKEY_ESCAPE)
211     StopCalibrationAndResetParams();
212 
213   key->StopPropagation();
214 }
215 
OnTouchEvent(ui::TouchEvent * touch)216 void TouchCalibratorController::OnTouchEvent(ui::TouchEvent* touch) {
217   if (!IsCalibrating())
218     return;
219   if (touch->type() != ui::ET_TOUCH_RELEASED)
220     return;
221   if (base::Time::Now() - last_touch_timestamp_ < kTouchIntervalThreshold)
222     return;
223   last_touch_timestamp_ = base::Time::Now();
224 
225   // If the touch event originated from a touch device that is associated with
226   // the internal display, then ignore it.
227   if (internal_touch_device_ids_.count(touch->source_device_id()))
228     return;
229 
230   if (touch_device_id_ == ui::InputDevice::kInvalidId) {
231     touch_device_id_ = touch->source_device_id();
232     event_transformer_ = CalculateEventTransformer(touch_device_id_);
233   }
234 
235   // If this is a custom touch calibration, then everything else is managed
236   // by the application responsible for the custom calibration UX.
237   if (state_ == CalibrationState::kCustomCalibration)
238     return;
239   touch->StopPropagation();
240 
241   TouchCalibratorView* target_screen_calibration_view =
242       static_cast<TouchCalibratorView*>(
243           touch_calibrator_widgets_[target_display_.id()]->GetContentsView());
244 
245   // If this is the final state, then store all calibration data and stop
246   // calibration.
247   if (target_screen_calibration_view->state() ==
248       TouchCalibratorView::CALIBRATION_COMPLETE) {
249     gfx::RectF calibration_bounds(
250         target_screen_calibration_view->GetLocalBounds());
251     Shell::Get()
252         ->window_tree_host_manager()
253         ->GetAshWindowTreeHostForDisplayId(target_display_.id())
254         ->AsWindowTreeHost()
255         ->GetRootTransform()
256         .TransformRect(&calibration_bounds);
257     CompleteCalibration(touch_point_quad_,
258                         gfx::ToRoundedSize(calibration_bounds.size()));
259     return;
260   }
261 
262   int state_index;
263   // Maps the state to an integer value. Assigns a non negative integral value
264   // for a state in which the user can interact with the the interface.
265   switch (target_screen_calibration_view->state()) {
266     case TouchCalibratorView::DISPLAY_POINT_1:
267       state_index = 0;
268       break;
269     case TouchCalibratorView::DISPLAY_POINT_2:
270       state_index = 1;
271       break;
272     case TouchCalibratorView::DISPLAY_POINT_3:
273       state_index = 2;
274       break;
275     case TouchCalibratorView::DISPLAY_POINT_4:
276       state_index = 3;
277       break;
278     default:
279       // Return early if the interface is in a state that does not allow user
280       // interaction.
281       return;
282   }
283 
284   // Store touch point corresponding to its display point.
285   gfx::Point display_point;
286   if (target_screen_calibration_view->GetDisplayPointLocation(&display_point)) {
287     // If the screen has a root transform applied, the display point does not
288     // correctly map to the touch point. This is specially evident if the
289     // display is rotated or a device scale factor is applied. The display point
290     // needs to have the root transform applied as well to correctly pair it
291     // with the touch point.
292     Shell::Get()
293         ->window_tree_host_manager()
294         ->GetAshWindowTreeHostForDisplayId(target_display_.id())
295         ->AsWindowTreeHost()
296         ->GetRootTransform()
297         .TransformPoint(&display_point);
298 
299     // Why do we need this? To understand this we need to know the life of an
300     // event location. The event location undergoes the following
301     // transformations along its path from the device to the event handler that
302     // is this class.
303     //
304     // Touch Device -> EventFactoryEvdev -> DrmWindowHost
305     //     -> WindowEventDispatcher -> EventHandler(this)
306     //
307     //  - The touch device dispatches the raw device event location. Lets assume
308     //    this is (x, y).
309     //  - The EventFactoryEvdev applies a touch transform that includes the
310     //    calibration information as well as an offset of the native bounds
311     //    of the display. This effectively converts the coordinates of the event
312     //    from the raw device event location to the native screen coordinates.
313     //    It gets the offset information from DrmWindowHost via the
314     //    ManagedDisplayInfo class. If the offset of the PlatformWindow is (A,B)
315     //    then the event location after this stage would be (x + A, y + B).
316     //  - The DrmWindowHost removes the offset from the event location so that
317     //    the location becomes relative to the platform window's origin. In
318     //    Chrome OS it so happens that each display is its own platform window.
319     //    So an offset equal to the display's origin in screen space is
320     //    subtracted from the event location. This effectively undoes the
321     //    previous step's transformation. Thus the event location after this
322     //    step is (x, y) again.
323     //  - WindowEventDispatcher applies an inverse root transform on the event
324     //    location. This means that if the display is rotated or has a device
325     //    scale factor, then those transformation are also applied to the event
326     //    location. It effectively converts the coordinates from platform window
327     //    coordinates to the aura's root window coordinates. The display in
328     //    context here is the display that is associated with the touch device
329     //    from which the event originated from.
330     //
331     // Up until the output of DrmWindowHost, everything is as expected. But
332     // WindowEventDispatcher applies an inverse root transform which modifies
333     // the raw event location that we wanted. Moreover, it modifies the raw
334     // event location using the root transform of the display that the touch
335     // device was previously associated with. To solve this, we need to undo the
336     // changes made to the event location by WindowEventDispatcher. This is what
337     // is achieved by |event_transformer_|.
338     gfx::PointF event_location_f(touch->location_f());
339     event_transformer_.TransformPoint(&event_location_f);
340 
341     touch_point_quad_[state_index] =
342         std::make_pair(display_point, gfx::ToRoundedPoint(event_location_f));
343   } else {
344     // TODO(malaykeshav): Display some kind of error for the user.
345     NOTREACHED() << "Touch calibration failed. Could not retrieve location for"
346                     " display point. Retry calibration.";
347   }
348 
349   target_screen_calibration_view->AdvanceToNextState();
350 }
351 
352 }  // namespace ash
353