| // Copyright 2016 The Chromium 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 "ash/display/touch_calibrator_controller.h" |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "ash/display/touch_calibrator_view.h" |
| #include "ash/display/window_tree_host_manager.h" |
| #include "ash/host/ash_window_tree_host.h" |
| #include "ash/shell.h" |
| #include "ash/touch/ash_touch_transform_controller.h" |
| #include "base/bind.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/display/manager/touch_device_manager.h" |
| #include "ui/display/screen.h" |
| #include "ui/events/devices/input_device_manager.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/gfx/geometry/size_conversions.h" |
| |
| namespace ash { |
| namespace { |
| |
| void InitInternalTouchDeviceIds(std::set<int>& internal_touch_device_ids) { |
| internal_touch_device_ids.clear(); |
| const std::vector<ui::TouchscreenDevice>& device_list = |
| ui::InputDeviceManager::GetInstance()->GetTouchscreenDevices(); |
| for (const auto& touchscreen_device : device_list) { |
| if (touchscreen_device.type == ui::InputDeviceType::INPUT_DEVICE_INTERNAL) |
| internal_touch_device_ids.insert(touchscreen_device.id); |
| } |
| } |
| |
| // Returns a transform to undo any transformations that are applied to events |
| // originating from the touch device identified with |touch_device_id|. This |
| // transform converts the event's location to the raw touch location. |
| gfx::Transform CalculateEventTransformer(int touch_device_id) { |
| const display::DisplayManager* display_manager = |
| Shell::Get()->display_manager(); |
| const std::vector<ui::TouchscreenDevice>& device_list = |
| ui::InputDeviceManager::GetInstance()->GetTouchscreenDevices(); |
| |
| auto device_it = std::find_if( |
| device_list.begin(), device_list.end(), |
| [&](const auto& device) { return device.id == touch_device_id; }); |
| DCHECK(device_it != device_list.end()) |
| << "Device id " << touch_device_id |
| << " is invalid. No such device connected to system"; |
| |
| int64_t previous_display_id = |
| display_manager->touch_device_manager()->GetAssociatedDisplay( |
| display::TouchDeviceIdentifier::FromDevice(*device_it)); |
| |
| // If the touch device is not associated with any display. This may happen in |
| // tests when the test does not setup the |ui::TouchDeviceTransform| before |
| // generating a touch event. |
| if (previous_display_id == display::kInvalidDisplayId) |
| return gfx::Transform(); |
| |
| // Undo the event transformations that the previous display applied on the |
| // event location. We want to store the raw event location information. |
| gfx::Transform tm = |
| Shell::Get() |
| ->window_tree_host_manager() |
| ->GetAshWindowTreeHostForDisplayId(previous_display_id) |
| ->AsWindowTreeHost() |
| ->GetRootTransform(); |
| return tm; |
| } |
| |
| } // namespace |
| |
| // Time interval after a touch event during which all other touch events are |
| // ignored during calibration. |
| const base::TimeDelta TouchCalibratorController::kTouchIntervalThreshold = |
| base::TimeDelta::FromMilliseconds(200); |
| |
| TouchCalibratorController::TouchCalibratorController() |
| : last_touch_timestamp_(base::Time::Now()) {} |
| |
| TouchCalibratorController::~TouchCalibratorController() { |
| touch_calibrator_views_.clear(); |
| StopCalibrationAndResetParams(); |
| } |
| |
| void TouchCalibratorController::OnDisplayConfigurationChanged() { |
| touch_calibrator_views_.clear(); |
| StopCalibrationAndResetParams(); |
| } |
| |
| void TouchCalibratorController::StartCalibration( |
| const display::Display& target_display, |
| bool is_custom_calibration, |
| TouchCalibrationCallback opt_callback) { |
| state_ = is_custom_calibration ? CalibrationState::kCustomCalibration |
| : CalibrationState::kNativeCalibration; |
| |
| if (opt_callback) |
| opt_callback_ = std::move(opt_callback); |
| |
| target_display_ = target_display; |
| |
| // Clear all touch calibrator views used in any previous calibration. |
| touch_calibrator_views_.clear(); |
| |
| // Set the touch device id as invalid so it can be set during calibration. |
| touch_device_id_ = ui::InputDevice::kInvalidId; |
| |
| // Populate |internal_touch_device_ids_| with the ids of touch devices that |
| // are currently associated with the internal display and are of type |
| // |ui::InputDeviceType::INPUT_DEVICE_INTERNAL|. |
| InitInternalTouchDeviceIds(internal_touch_device_ids_); |
| |
| // If this is a native touch calibration, then initialize the UX for it. |
| if (state_ == CalibrationState::kNativeCalibration) { |
| Shell::Get()->window_tree_host_manager()->AddObserver(this); |
| |
| // Reset the calibration data. |
| touch_point_quad_.fill(std::make_pair(gfx::Point(0, 0), gfx::Point(0, 0))); |
| |
| std::vector<display::Display> displays = |
| display::Screen::GetScreen()->GetAllDisplays(); |
| |
| for (const display::Display& display : displays) { |
| bool is_primary_view = display.id() == target_display_.id(); |
| touch_calibrator_views_[display.id()] = |
| std::make_unique<TouchCalibratorView>(display, is_primary_view); |
| } |
| } |
| |
| Shell::Get()->touch_transformer_controller()->SetForCalibration(true); |
| |
| // Add self as an event handler target. |
| Shell::Get()->AddPreTargetHandler(this); |
| } |
| |
| void TouchCalibratorController::StopCalibrationAndResetParams() { |
| if (!IsCalibrating()) |
| return; |
| Shell::Get()->window_tree_host_manager()->RemoveObserver(this); |
| |
| Shell::Get()->touch_transformer_controller()->SetForCalibration(false); |
| |
| // Remove self as the event handler. |
| Shell::Get()->RemovePreTargetHandler(this); |
| |
| // Transition all touch calibrator views to their final state for a graceful |
| // exit if this is touch calibration with native UX. |
| if (state_ == CalibrationState::kNativeCalibration) { |
| for (const auto& it : touch_calibrator_views_) |
| it.second->SkipToFinalState(); |
| } |
| |
| state_ = CalibrationState::kInactive; |
| |
| if (opt_callback_) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(opt_callback_), false /* failure */)); |
| opt_callback_.Reset(); |
| } |
| } |
| |
| void TouchCalibratorController::CompleteCalibration( |
| const CalibrationPointPairQuad& pairs, |
| const gfx::Size& display_size) { |
| bool did_find_touch_device = false; |
| display::TouchDeviceIdentifier touch_device_identifier = |
| display::TouchDeviceIdentifier::GetFallbackTouchDeviceIdentifier(); |
| |
| const std::vector<ui::TouchscreenDevice>& device_list = |
| ui::InputDeviceManager::GetInstance()->GetTouchscreenDevices(); |
| for (const auto& device : device_list) { |
| if (device.id == touch_device_id_) { |
| touch_device_identifier = |
| display::TouchDeviceIdentifier::FromDevice(device); |
| did_find_touch_device = true; |
| break; |
| } |
| } |
| |
| if (!did_find_touch_device) { |
| VLOG(1) << "No touch device with id: " << touch_device_id_ << " found to " |
| << "complete touch calibration for display with id: " |
| << target_display_.id() << ". Storing it as a fallback"; |
| } else if (touch_device_identifier == |
| display::TouchDeviceIdentifier:: |
| GetFallbackTouchDeviceIdentifier()) { |
| LOG(ERROR) |
| << "Hash collision in generating touch device identifier for " |
| << " device. Hash Generated: " << touch_device_identifier |
| << " || Fallback touch device identifier: " |
| << display::TouchDeviceIdentifier::GetFallbackTouchDeviceIdentifier(); |
| } |
| |
| if (opt_callback_) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(opt_callback_), true /* success */)); |
| opt_callback_.Reset(); |
| } |
| StopCalibrationAndResetParams(); |
| Shell::Get()->display_manager()->SetTouchCalibrationData( |
| target_display_.id(), pairs, display_size, touch_device_identifier); |
| } |
| |
| bool TouchCalibratorController::IsCalibrating() const { |
| return state_ != CalibrationState::kInactive; |
| } |
| |
| // ui::EventHandler: |
| void TouchCalibratorController::OnKeyEvent(ui::KeyEvent* key) { |
| if (state_ != CalibrationState::kNativeCalibration) |
| return; |
| // Detect ESC key press. |
| if (key->type() == ui::ET_KEY_PRESSED && key->key_code() == ui::VKEY_ESCAPE) |
| StopCalibrationAndResetParams(); |
| |
| key->StopPropagation(); |
| } |
| |
| void TouchCalibratorController::OnTouchEvent(ui::TouchEvent* touch) { |
| if (!IsCalibrating()) |
| return; |
| if (touch->type() != ui::ET_TOUCH_RELEASED) |
| return; |
| if (base::Time::Now() - last_touch_timestamp_ < kTouchIntervalThreshold) |
| return; |
| last_touch_timestamp_ = base::Time::Now(); |
| |
| // If the touch event originated from a touch device that is associated with |
| // the internal display, then ignore it. |
| if (internal_touch_device_ids_.count(touch->source_device_id())) |
| return; |
| |
| if (touch_device_id_ == ui::InputDevice::kInvalidId) { |
| touch_device_id_ = touch->source_device_id(); |
| event_transformer_ = CalculateEventTransformer(touch_device_id_); |
| } |
| |
| // If this is a custom touch calibration, then everything else is managed |
| // by the application responsible for the custom calibration UX. |
| if (state_ == CalibrationState::kCustomCalibration) |
| return; |
| touch->StopPropagation(); |
| |
| TouchCalibratorView* target_screen_calibration_view = |
| touch_calibrator_views_[target_display_.id()].get(); |
| |
| // If this is the final state, then store all calibration data and stop |
| // calibration. |
| if (target_screen_calibration_view->state() == |
| TouchCalibratorView::CALIBRATION_COMPLETE) { |
| gfx::RectF calibration_bounds( |
| target_screen_calibration_view->GetLocalBounds()); |
| Shell::Get() |
| ->window_tree_host_manager() |
| ->GetAshWindowTreeHostForDisplayId(target_display_.id()) |
| ->AsWindowTreeHost() |
| ->GetRootTransform() |
| .TransformRect(&calibration_bounds); |
| CompleteCalibration(touch_point_quad_, |
| gfx::ToRoundedSize(calibration_bounds.size())); |
| return; |
| } |
| |
| int state_index; |
| // Maps the state to an integer value. Assigns a non negative integral value |
| // for a state in which the user can interact with the the interface. |
| switch (target_screen_calibration_view->state()) { |
| case TouchCalibratorView::DISPLAY_POINT_1: |
| state_index = 0; |
| break; |
| case TouchCalibratorView::DISPLAY_POINT_2: |
| state_index = 1; |
| break; |
| case TouchCalibratorView::DISPLAY_POINT_3: |
| state_index = 2; |
| break; |
| case TouchCalibratorView::DISPLAY_POINT_4: |
| state_index = 3; |
| break; |
| default: |
| // Return early if the interface is in a state that does not allow user |
| // interaction. |
| return; |
| } |
| |
| // Store touch point corresponding to its display point. |
| gfx::Point display_point; |
| if (target_screen_calibration_view->GetDisplayPointLocation(&display_point)) { |
| // If the screen has a root transform applied, the display point does not |
| // correctly map to the touch point. This is specially evident if the |
| // display is rotated or a device scale factor is applied. The display point |
| // needs to have the root transform applied as well to correctly pair it |
| // with the touch point. |
| Shell::Get() |
| ->window_tree_host_manager() |
| ->GetAshWindowTreeHostForDisplayId(target_display_.id()) |
| ->AsWindowTreeHost() |
| ->GetRootTransform() |
| .TransformPoint(&display_point); |
| |
| // Why do we need this? To understand this we need to know the life of an |
| // event location. The event location undergoes the following |
| // transformations along its path from the device to the event handler that |
| // is this class. |
| // |
| // Touch Device -> EventFactoryEvdev -> DrmWindowHost |
| // -> WindowEventDispatcher -> EventHandler(this) |
| // |
| // - The touch device dispatches the raw device event location. Lets assume |
| // this is (x, y). |
| // - The EventFactoryEvdev applies a touch transform that includes the |
| // calibration information as well as an offset of the native bounds |
| // of the display. This effectively converts the coordinates of the event |
| // from the raw device event location to the native screen coordinates. |
| // It gets the offset information from DrmWindowHost via the |
| // ManagedDisplayInfo class. If the offset of the PlatformWindow is (A,B) |
| // then the event location after this stage would be (x + A, y + B). |
| // - The DrmWindowHost removes the offset from the event location so that |
| // the location becomes relative to the platform window's origin. In |
| // Chrome OS it so happens that each display is its own platform window. |
| // So an offset equal to the display's origin in screen space is |
| // subtracted from the event location. This effectively undoes the |
| // previous step's transformation. Thus the event location after this |
| // step is (x, y) again. |
| // - WindowEventDispatcher applies an inverse root transform on the event |
| // location. This means that if the display is rotated or has a device |
| // scale factor, then those transformation are also applied to the event |
| // location. It effectively converts the coordinates from platform window |
| // coordinates to the aura's root window coordinates. The display in |
| // context here is the display that is associated with the touch device |
| // from which the event originated from. |
| // |
| // Up until the output of DrmWindowHost, everything is as expected. But |
| // WindowEventDispatcher applies an inverse root transform which modifies |
| // the raw event location that we wanted. Moreover, it modifies the raw |
| // event location using the root transform of the display that the touch |
| // device was previously associated with. To solve this, we need to undo the |
| // changes made to the event location by WindowEventDispatcher. This is what |
| // is achieved by |event_transformer_|. |
| gfx::PointF event_location_f(touch->location_f()); |
| event_transformer_.TransformPoint(&event_location_f); |
| |
| touch_point_quad_[state_index] = |
| std::make_pair(display_point, gfx::ToRoundedPoint(event_location_f)); |
| } else { |
| // TODO(malaykeshav): Display some kind of error for the user. |
| NOTREACHED() << "Touch calibration failed. Could not retrieve location for" |
| " display point. Retry calibration."; |
| } |
| |
| target_screen_calibration_view->AdvanceToNextState(); |
| } |
| |
| } // namespace ash |