Commit a2a44ba5 authored by lanwei's avatar lanwei Committed by Commit bot

Correctly release the touch events on Lenovo Horizon device.

The Lenovo Horizon device displays the wrong number of the touch pointers when all fingers released from the screen. Because HWND Message sometimes sends inconsistent number of touch pointers, we keep the IDs of touch pointers we have seen from last time, and release the ones which are not in the current message.

BUG=316085

Review URL: https://codereview.chromium.org/988473002

Cr-Commit-Position: refs/heads/master@{#329770}
parent c8337810
......@@ -567,6 +567,7 @@
'widget/root_view_unittest.cc',
'widget/widget_unittest.cc',
'widget/window_reorderer_unittest.cc',
'win/hwnd_message_handler_unittest.cc',
'window/custom_frame_view_unittest.cc',
'window/dialog_client_view_unittest.cc',
'window/dialog_delegate_unittest.cc',
......
......@@ -10,6 +10,7 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/profiler/scoped_tracker.h"
#include "base/trace_event/trace_event.h"
#include "base/tracked_objects.h"
......@@ -328,6 +329,7 @@ HWNDMessageHandler::HWNDMessageHandler(HWNDMessageHandlerDelegate* delegate)
last_mouse_hwheel_time_(0),
msg_handled_(FALSE),
dwm_transition_desired_(false),
active_touch_point_count_(0),
autohide_factory_(this),
weak_factory_(this) {
}
......@@ -2297,8 +2299,6 @@ LRESULT HWNDMessageHandler::OnTouchEvent(UINT message,
if (ui::GetTouchInputInfoWrapper(reinterpret_cast<HTOUCHINPUT>(l_param),
num_points, input.get(),
sizeof(TOUCHINPUT))) {
int flags = ui::GetModifiersFromKeyState();
TouchEvents touch_events;
for (int i = 0; i < num_points; ++i) {
POINT point;
point.x = TOUCH_COORD_TO_PIXEL(input[i].x);
......@@ -2314,50 +2314,18 @@ LRESULT HWNDMessageHandler::OnTouchEvent(UINT message,
if (hittest != HTCLIENT)
return 0;
}
ScreenToClient(hwnd(), &point);
last_touch_message_time_ = ::GetMessageTime();
ui::EventType touch_event_type = ui::ET_UNKNOWN;
if (input[i].dwFlags & TOUCHEVENTF_DOWN) {
touch_ids_.insert(input[i].dwID);
touch_event_type = ui::ET_TOUCH_PRESSED;
touch_down_contexts_++;
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&HWNDMessageHandler::ResetTouchDownContext,
weak_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(kTouchDownContextResetTimeout));
} else if (input[i].dwFlags & TOUCHEVENTF_UP) {
touch_ids_.erase(input[i].dwID);
touch_event_type = ui::ET_TOUCH_RELEASED;
} else if (input[i].dwFlags & TOUCHEVENTF_MOVE) {
touch_event_type = ui::ET_TOUCH_MOVED;
}
if (touch_event_type != ui::ET_UNKNOWN) {
// input[i].dwTime doesn't necessarily relate to the system time at all,
// so use base::TimeTicks::Now()
const base::TimeTicks now = base::TimeTicks::Now();
ui::TouchEvent event(touch_event_type,
gfx::Point(point.x, point.y),
id_generator_.GetGeneratedID(input[i].dwID),
now - base::TimeTicks());
event.set_flags(flags);
event.latency()->AddLatencyNumberWithTimestamp(
ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
0,
0,
base::TimeTicks::FromInternalValue(
event.time_stamp().ToInternalValue()),
1);
touch_events.push_back(event);
if (touch_event_type == ui::ET_TOUCH_RELEASED)
id_generator_.ReleaseNumber(input[i].dwID);
}
}
TouchEvents touch_events;
int old_touch_down_contexts = touch_down_contexts_;
PrepareTouchEventList(input.get(), num_points, &touch_events);
int new_touch_presses = touch_down_contexts_ - old_touch_down_contexts;
if (new_touch_presses > 0) {
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE, base::Bind(&HWNDMessageHandler::DecrementTouchDownContext,
weak_factory_.GetWeakPtr(), new_touch_presses),
base::TimeDelta::FromMilliseconds(kTouchDownContextResetTimeout));
}
// Handle the touch events asynchronously. We need this because touch
// events on windows don't fire if we enter a modal loop in the context of
// a touch event.
......@@ -2371,6 +2339,103 @@ LRESULT HWNDMessageHandler::OnTouchEvent(UINT message,
return 0;
}
void HWNDMessageHandler::PrepareTouchEventList(TOUCHINPUT input[],
int num_points,
TouchEvents* touch_events) {
for (int i = 0; i < num_points; ++i) {
POINT point;
point.x = TOUCH_COORD_TO_PIXEL(input[i].x);
point.y = TOUCH_COORD_TO_PIXEL(input[i].y);
gfx::Point point_location(point.x, point.y);
ScreenToClient(hwnd(), &point);
// TOUCHEVENTF_DOWN cannot be combined with TOUCHEVENTF_MOVE or
// TOUCHEVENTF_UP, but TOUCHEVENTF_MOVE and TOUCHEVENTF_UP can be combined
// in one input.
if (input[i].dwFlags & TOUCHEVENTF_DOWN) {
touch_down_contexts_++;
active_touch_point_count_++;
GenerateTouchEvent(input[i].dwID, point_location, ui::ET_TOUCH_PRESSED,
touch_events);
} else {
if (input[i].dwFlags & TOUCHEVENTF_MOVE) {
GenerateTouchEvent(input[i].dwID, point_location, ui::ET_TOUCH_MOVED,
touch_events);
}
if (input[i].dwFlags & TOUCHEVENTF_UP) {
active_touch_point_count_--;
GenerateTouchEvent(input[i].dwID, point_location, ui::ET_TOUCH_RELEASED,
touch_events);
}
}
}
last_touch_message_time_ = ::GetMessageTime();
UpdateTouchPointStates(touch_events);
}
void HWNDMessageHandler::GenerateTouchEvent(DWORD input_dwID,
const gfx::Point& point_location,
ui::EventType touch_event_type,
TouchEvents* touch_events) {
int touch_id = static_cast<int>(id_generator_.GetGeneratedID(input_dwID));
if (touch_id < 0 || touch_id >= static_cast<int>(touch_id_list_.size())) {
return;
}
TouchPoint& touch_point = touch_id_list_[touch_id];
int flags = ui::GetModifiersFromKeyState();
// The dwTime of every input in the WM_TOUCH message doesn't necessarily
// relate to the system time at all, so use base::TimeTicks::Now() for
// touchevent time.
base::TimeDelta now = ui::EventTimeForNow();
// We set a check to assert that we do not have missing touch presses in
// every message.
bool has_missing_touch_press = touch_event_type != ui::ET_TOUCH_PRESSED &&
touch_point.in_touch_list == InTouchList::NotPresent;
CHECK(!has_missing_touch_press) << "There are missing touch presses";
ui::TouchEvent event(touch_event_type, point_location, touch_id, now);
event.set_flags(flags);
event.latency()->AddLatencyNumberWithTimestamp(
ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, 0, base::TimeTicks::Now(),
1);
touch_events->push_back(event);
// Mark the active touch pointers in the touch_id_list_ to be
// InCurrentMessage, so we can track the ones which are missed in the
// current message and release them.
if (touch_event_type == ui::ET_TOUCH_RELEASED) {
id_generator_.ReleaseNumber(input_dwID);
touch_point.in_touch_list = InTouchList::NotPresent;
} else {
touch_point.in_touch_list = InTouchList::InCurrentMessage;
touch_point.location = point_location;
}
}
void HWNDMessageHandler::UpdateTouchPointStates(TouchEvents* touch_events) {
for (size_t i = 0; i != touch_id_list_.size(); ++i) {
// Loop through the touch pointers list, if we see any which is only in
// the previous message, we will send a touch release event for this ID.
TouchPoint& touch_point = touch_id_list_[i];
if (touch_point.in_touch_list == InTouchList::InPreviousMessage) {
base::TimeDelta now = base::TimeTicks::Now() - base::TimeTicks();
ui::TouchEvent event(ui::ET_TOUCH_RELEASED, touch_point.location,
static_cast<int>(i), now);
touch_events->push_back(event);
touch_point.in_touch_list = InTouchList::NotPresent;
id_generator_.ReleaseGeneratedID(i);
active_touch_point_count_--;
} else if (touch_point.in_touch_list == InTouchList::InCurrentMessage) {
touch_point.in_touch_list = InTouchList::InPreviousMessage;
}
}
}
void HWNDMessageHandler::OnWindowPosChanging(WINDOWPOS* window_pos) {
// TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
tracked_objects::ScopedTracker tracking_profile(
......@@ -2505,15 +2570,15 @@ void HWNDMessageHandler::HandleTouchEvents(const TouchEvents& touch_events) {
delegate_->HandleTouchEvent(touch_events[i]);
}
void HWNDMessageHandler::ResetTouchDownContext() {
touch_down_contexts_--;
void HWNDMessageHandler::DecrementTouchDownContext(int decrement) {
touch_down_contexts_ -= decrement;
}
LRESULT HWNDMessageHandler::HandleMouseEventInternal(UINT message,
WPARAM w_param,
LPARAM l_param,
bool track_mouse) {
if (!touch_ids_.empty())
if (active_touch_point_count_)
return 0;
// TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
......
......@@ -7,7 +7,7 @@
#include <windows.h>
#include <set>
#include <array>
#include <vector>
#include "base/basictypes.h"
......@@ -21,6 +21,7 @@
#include "ui/base/ui_base_types.h"
#include "ui/base/win/window_event_target.h"
#include "ui/events/event.h"
#include "ui/events/gesture_detection/motion_event.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/sequential_id_generator.h"
#include "ui/gfx/win/window_impl.h"
......@@ -206,7 +207,26 @@ class VIEWS_EXPORT HWNDMessageHandler :
void SizeConstraintsChanged();
private:
typedef std::set<DWORD> TouchIDs;
FRIEND_TEST_ALL_PREFIXES(HWNDMessageHandler, TestCorrectTouchInputList);
FRIEND_TEST_ALL_PREFIXES(HWNDMessageHandler, TestCombineTouchMoveAndUp);
FRIEND_TEST_ALL_PREFIXES(HWNDMessageHandler, TestMissingTouchRelease);
// We use InTouchList self-defined type to specify if the touch point from
// last message is in the current message or not.
enum class InTouchList { NotPresent, InPreviousMessage, InCurrentMessage };
// We need the touch location to release a touch point which is not in the
// current message.
struct TouchPoint {
gfx::Point location;
InTouchList in_touch_list;
TouchPoint() : in_touch_list(InTouchList::NotPresent) {}
};
using TouchEvents = std::vector<ui::TouchEvent>;
using TouchPointArray =
std::array<TouchPoint, ui::MotionEvent::MAX_TOUCH_POINT_COUNT>;
// Overridden from internal::InputMethodDelegate:
void DispatchKeyEventPostIME(const ui::KeyEvent& key) override;
......@@ -453,7 +473,6 @@ class VIEWS_EXPORT HWNDMessageHandler :
// Receives Windows Session Change notifications.
void OnSessionChange(WPARAM status_code);
typedef std::vector<ui::TouchEvent> TouchEvents;
// Helper to handle the list of touch events passed in. We need this because
// touch events on windows don't fire if we enter a modal loop in the context
// of a touch event.
......@@ -461,7 +480,7 @@ class VIEWS_EXPORT HWNDMessageHandler :
// Resets the flag which indicates that we are in the context of a touch down
// event.
void ResetTouchDownContext();
void DecrementTouchDownContext(int decrement);
// Helper to handle mouse events.
// The |message|, |w_param|, |l_param| parameters identify the Windows mouse
......@@ -484,6 +503,23 @@ class VIEWS_EXPORT HWNDMessageHandler :
// Provides functionality to transition a frame to DWM.
void PerformDwmTransition();
void PrepareTouchEventList(TOUCHINPUT input[],
int num_points,
TouchEvents* touch_events);
// From one WM_TOUCH message, we will generate corresponding touch events.
void GenerateTouchEvent(DWORD input_dwID,
const gfx::Point& point_location,
ui::EventType touch_event_type,
TouchEvents* touch_events);
// It will release the touch points which are no longer in the current message
// because the messages are sent inconsistently, and also set the touch points
// we have seen in the current message to be InPreviousMessage.
void UpdateTouchPointStates(TouchEvents* touch_events);
const TouchPointArray& touch_id_list() const { return touch_id_list_; }
HWNDMessageHandlerDelegate* delegate_;
scoped_ptr<FullscreenHandler> fullscreen_handler_;
......@@ -517,9 +553,6 @@ class VIEWS_EXPORT HWNDMessageHandler :
// area. We need this so we can correctly show the context menu on mouse-up.
bool is_right_mouse_pressed_on_caption_;
// The set of touch devices currently down.
TouchIDs touch_ids_;
// ScopedRedrawLock ----------------------------------------------------------
// Represents the number of ScopedRedrawLocks active against this widget.
......@@ -588,6 +621,16 @@ class VIEWS_EXPORT HWNDMessageHandler :
// Manages observation of Windows Session Change messages.
scoped_ptr<WindowsSessionChangeObserver> windows_session_change_observer_;
// HWND Message sometimes sends inconsistent number of touch pointers, so we
// keep the IDs of touch pointers we have seen from last message, and compare
// with the current message in order to release the ones which are not in the
// current list, or send a touchpress when we see a new touch point. The
// index of the array is the touch_ID.
TouchPointArray touch_id_list_;
// Keep the count of the current active touch points.
int active_touch_point_count_;
// The WeakPtrFactories below must occur last in the class definition so they
// get destroyed last.
......
// Copyright 2015 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 "ui/views/win/hwnd_message_handler.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace views {
namespace {
TOUCHINPUT GenerateTouchInput(int id, int x, int y, DWORD dwFlags) {
TOUCHINPUT input;
input.dwID = id;
input.x = x;
input.y = y;
input.dwFlags = dwFlags;
return input;
}
} // namespace
TEST(HWNDMessageHandler, TestCorrectTouchInputList) {
HWNDMessageHandler messageHandler(NULL);
HWNDMessageHandler::TouchEvents touch_events1;
// One finger down.
scoped_ptr<TOUCHINPUT[]> input(new TOUCHINPUT[10]);
input[0] = GenerateTouchInput(1, 10, 10, TOUCHEVENTF_DOWN);
// Send out one touchpress event and set value to be
// InPreviousMessage at index 0 in touch_id_list.
messageHandler.PrepareTouchEventList(input.get(), 1, &touch_events1);
EXPECT_EQ(1, touch_events1.size());
EXPECT_EQ(ui::ET_TOUCH_PRESSED, touch_events1[0].type());
EXPECT_EQ(0, touch_events1[0].touch_id());
EXPECT_EQ(messageHandler.touch_id_list()[0].in_touch_list,
HWNDMessageHandler::InTouchList::InPreviousMessage);
EXPECT_EQ(touch_events1[0].location(),
messageHandler.touch_id_list()[0].location);
// One finger move and two fingers down.
HWNDMessageHandler::TouchEvents touch_events2;
input[0] = GenerateTouchInput(1, 20, 20, TOUCHEVENTF_MOVE);
input[1] = GenerateTouchInput(2, 30, 30, TOUCHEVENTF_DOWN);
input[2] = GenerateTouchInput(3, 40, 40, TOUCHEVENTF_DOWN);
// Send out touchmove and two touchpress events and touch_id_list has
// three InPreviousMessage at index 0, 1, 2.
messageHandler.PrepareTouchEventList(input.get(), 3, &touch_events2);
EXPECT_EQ(3, touch_events2.size());
EXPECT_EQ(ui::ET_TOUCH_MOVED, touch_events2[0].type());
EXPECT_EQ(0, touch_events2[0].touch_id());
EXPECT_EQ(messageHandler.touch_id_list()[0].in_touch_list,
HWNDMessageHandler::InTouchList::InPreviousMessage);
EXPECT_EQ(messageHandler.touch_id_list()[1].in_touch_list,
HWNDMessageHandler::InTouchList::InPreviousMessage);
EXPECT_EQ(messageHandler.touch_id_list()[2].in_touch_list,
HWNDMessageHandler::InTouchList::InPreviousMessage);
// Release one finger and two finger moves.
HWNDMessageHandler::TouchEvents touch_events3;
input[0] = GenerateTouchInput(1, 22, 22, TOUCHEVENTF_UP);
input[1] = GenerateTouchInput(2, 33, 33, TOUCHEVENTF_MOVE);
input[2] = GenerateTouchInput(3, 44, 44, TOUCHEVENTF_MOVE);
// Send out one touchrelease, two touch move events and touch_id_list has
// two InPreviousMessage at index 1, 2 and one NotPresent at index 0.
messageHandler.PrepareTouchEventList(input.get(), 3, &touch_events3);
EXPECT_EQ(3, touch_events3.size());
EXPECT_EQ(ui::ET_TOUCH_RELEASED, touch_events3[0].type());
EXPECT_EQ(ui::ET_TOUCH_MOVED, touch_events3[1].type());
EXPECT_EQ(ui::ET_TOUCH_MOVED, touch_events3[2].type());
EXPECT_EQ(0, touch_events3[0].touch_id());
EXPECT_EQ(1, touch_events3[1].touch_id());
EXPECT_EQ(2, touch_events3[2].touch_id());
EXPECT_EQ(messageHandler.touch_id_list()[0].in_touch_list,
HWNDMessageHandler::InTouchList::NotPresent);
EXPECT_EQ(messageHandler.touch_id_list()[1].in_touch_list,
HWNDMessageHandler::InTouchList::InPreviousMessage);
EXPECT_EQ(messageHandler.touch_id_list()[2].in_touch_list,
HWNDMessageHandler::InTouchList::InPreviousMessage);
}
TEST(HWNDMessageHandler, TestCombineTouchMoveAndUp) {
HWNDMessageHandler messageHandler(NULL);
HWNDMessageHandler::TouchEvents touch_events1;
// Two fingers down.
scoped_ptr<TOUCHINPUT[]> input(new TOUCHINPUT[10]);
input[0] = GenerateTouchInput(1, 10, 10, TOUCHEVENTF_DOWN);
input[1] = GenerateTouchInput(2, 20, 20, TOUCHEVENTF_DOWN);
// Send out two touchpress events and touch_id_list has two
// InPreviousMessage.
messageHandler.PrepareTouchEventList(input.get(), 2, &touch_events1);
EXPECT_EQ(2, touch_events1.size());
EXPECT_EQ(ui::ET_TOUCH_PRESSED, touch_events1[0].type());
EXPECT_EQ(ui::ET_TOUCH_PRESSED, touch_events1[1].type());
EXPECT_EQ(0, touch_events1[0].touch_id());
EXPECT_EQ(1, touch_events1[1].touch_id());
EXPECT_EQ(messageHandler.touch_id_list()[0].in_touch_list,
HWNDMessageHandler::InTouchList::InPreviousMessage);
EXPECT_EQ(messageHandler.touch_id_list()[1].in_touch_list,
HWNDMessageHandler::InTouchList::InPreviousMessage);
// One finger move and up and one fingers move.
HWNDMessageHandler::TouchEvents touch_events2;
input[0] = GenerateTouchInput(1, 20, 20,
TOUCHEVENTF_MOVE | TOUCHEVENTF_UP);
input[1] = GenerateTouchInput(2, 30, 30, TOUCHEVENTF_MOVE);
// Send out a touchmove, touchrelease and a touchmove events and
// touch_id_list has one InPreviousMessage at index 1 and one NotPresent
// at index 0.
messageHandler.PrepareTouchEventList(input.get(), 2, &touch_events2);
EXPECT_EQ(3, touch_events2.size());
EXPECT_EQ(ui::ET_TOUCH_MOVED, touch_events2[0].type());
EXPECT_EQ(ui::ET_TOUCH_RELEASED, touch_events2[1].type());
EXPECT_EQ(ui::ET_TOUCH_MOVED, touch_events2[2].type());
EXPECT_EQ(0, touch_events2[0].touch_id());
EXPECT_EQ(0, touch_events2[1].touch_id());
EXPECT_EQ(1, touch_events2[2].touch_id());
EXPECT_EQ(messageHandler.touch_id_list()[0].in_touch_list,
HWNDMessageHandler::InTouchList::NotPresent);
EXPECT_EQ(messageHandler.touch_id_list()[1].in_touch_list,
HWNDMessageHandler::InTouchList::InPreviousMessage);
}
TEST(HWNDMessageHandler, TestMissingTouchRelease) {
HWNDMessageHandler messageHandler(NULL);
HWNDMessageHandler::TouchEvents touch_events1;
// Two fingers down.
scoped_ptr<TOUCHINPUT[]> input(new TOUCHINPUT[10]);
input[0] = GenerateTouchInput(1, 10, 10, TOUCHEVENTF_DOWN);
input[1] = GenerateTouchInput(2, 20, 20, TOUCHEVENTF_DOWN);
// Send out two touchpress events and touch_id_list has two
// InPreviousMessage.
messageHandler.PrepareTouchEventList(input.get(), 2, &touch_events1);
EXPECT_EQ(2, touch_events1.size());
EXPECT_EQ(ui::ET_TOUCH_PRESSED, touch_events1[0].type());
EXPECT_EQ(ui::ET_TOUCH_PRESSED, touch_events1[1].type());
EXPECT_EQ(0, touch_events1[0].touch_id());
EXPECT_EQ(1, touch_events1[1].touch_id());
EXPECT_EQ(messageHandler.touch_id_list()[0].in_touch_list,
HWNDMessageHandler::InTouchList::InPreviousMessage);
EXPECT_EQ(messageHandler.touch_id_list()[1].in_touch_list,
HWNDMessageHandler::InTouchList::InPreviousMessage);
// Only one finger down, so we miss two touchrelease.
HWNDMessageHandler::TouchEvents touch_events2;
input[0] = GenerateTouchInput(3, 30, 30, TOUCHEVENTF_DOWN);
// Send out one touchpress and two touchrelease events for the touch ids
// which are not in this input list, and touch_id_list has one
// InPreviousMessage at index 2.
messageHandler.PrepareTouchEventList(input.get(), 1, &touch_events2);
EXPECT_EQ(3, touch_events2.size());
EXPECT_EQ(ui::ET_TOUCH_PRESSED, touch_events2[0].type());
EXPECT_EQ(ui::ET_TOUCH_RELEASED, touch_events2[1].type());
EXPECT_EQ(ui::ET_TOUCH_RELEASED, touch_events2[2].type());
EXPECT_EQ(2, touch_events2[0].touch_id());
EXPECT_EQ(0, touch_events2[1].touch_id());
EXPECT_EQ(1, touch_events2[2].touch_id());
EXPECT_EQ(messageHandler.touch_id_list()[0].in_touch_list,
HWNDMessageHandler::InTouchList::NotPresent);
EXPECT_EQ(messageHandler.touch_id_list()[1].in_touch_list,
HWNDMessageHandler::InTouchList::NotPresent);
EXPECT_EQ(messageHandler.touch_id_list()[2].in_touch_list,
HWNDMessageHandler::InTouchList::InPreviousMessage);
}
} // namespace views
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment