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