1 // Copyright 2014 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 "ui/chromeos/touch_exploration_controller.h" 6 7 #include "base/logging.h" 8 #include "base/strings/string_number_conversions.h" 9 #include "ui/aura/client/cursor_client.h" 10 #include "ui/aura/window.h" 11 #include "ui/aura/window_event_dispatcher.h" 12 #include "ui/aura/window_tree_host.h" 13 #include "ui/events/event.h" 14 #include "ui/events/event_processor.h" 15 16 #define VLOG_STATE() if (VLOG_IS_ON(0)) VlogState(__func__) 17 #define VLOG_EVENT(event) if (VLOG_IS_ON(0)) VlogEvent(event, __func__) 18 19 namespace ui { 20 21 namespace { 22 // The default value for initial_touch_id_passthrough_mapping_ used 23 // when the user has not yet released any fingers yet, so there's no 24 // touch id remapping yet. 25 const int kTouchIdUnassigned = 0; 26 27 // The value for initial_touch_id_passthrough_mapping_ if the user has 28 // released the first finger but some other fingers are held down. In this 29 // state we don't do any touch id remapping, but we distinguish it from the 30 // kTouchIdUnassigned state because we don't want to assign 31 // initial_touch_id_passthrough_mapping_ a touch id anymore, 32 // until all fingers are released. 33 const int kTouchIdNone = -1; 34 } // namespace 35 36 TouchExplorationController::TouchExplorationController( 37 aura::Window* root_window) 38 : root_window_(root_window), 39 initial_touch_id_passthrough_mapping_(kTouchIdUnassigned), 40 state_(NO_FINGERS_DOWN), 41 event_handler_for_testing_(NULL), 42 prev_state_(NO_FINGERS_DOWN) { 43 CHECK(root_window); 44 root_window->GetHost()->GetEventSource()->AddEventRewriter(this); 45 } 46 47 TouchExplorationController::~TouchExplorationController() { 48 root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this); 49 } 50 51 void TouchExplorationController::CallTapTimerNowForTesting() { 52 DCHECK(tap_timer_.IsRunning()); 53 tap_timer_.Stop(); 54 OnTapTimerFired(); 55 } 56 57 void TouchExplorationController::SetEventHandlerForTesting( 58 ui::EventHandler* event_handler_for_testing) { 59 event_handler_for_testing_ = event_handler_for_testing; 60 } 61 62 bool TouchExplorationController::IsInNoFingersDownStateForTesting() const { 63 return state_ == NO_FINGERS_DOWN; 64 } 65 66 ui::EventRewriteStatus TouchExplorationController::RewriteEvent( 67 const ui::Event& event, 68 scoped_ptr<ui::Event>* rewritten_event) { 69 if (!event.IsTouchEvent()) 70 return ui::EVENT_REWRITE_CONTINUE; 71 const ui::TouchEvent& touch_event = 72 static_cast<const ui::TouchEvent&>(event); 73 74 // If the tap timer should have fired by now but hasn't, run it now and 75 // stop the timer. This is important so that behavior is consistent with 76 // the timestamps of the events, and not dependent on the granularity of 77 // the timer. 78 if (tap_timer_.IsRunning() && 79 touch_event.time_stamp() - initial_press_->time_stamp() > 80 gesture_detector_config_.double_tap_timeout) { 81 tap_timer_.Stop(); 82 OnTapTimerFired(); 83 // Note: this may change the state. We should now continue and process 84 // this event under this new state. 85 } 86 87 const ui::EventType type = touch_event.type(); 88 const gfx::PointF& location = touch_event.location_f(); 89 const int touch_id = touch_event.touch_id(); 90 91 // Always update touch ids and touch locations, so we can use those 92 // no matter what state we're in. 93 if (type == ui::ET_TOUCH_PRESSED) { 94 current_touch_ids_.push_back(touch_id); 95 touch_locations_.insert(std::pair<int, gfx::PointF>(touch_id, location)); 96 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { 97 std::vector<int>::iterator it = std::find( 98 current_touch_ids_.begin(), current_touch_ids_.end(), touch_id); 99 100 // Can happen if touch exploration is enabled while fingers were down. 101 if (it == current_touch_ids_.end()) 102 return ui::EVENT_REWRITE_CONTINUE; 103 104 current_touch_ids_.erase(it); 105 touch_locations_.erase(touch_id); 106 } else if (type == ui::ET_TOUCH_MOVED) { 107 std::vector<int>::iterator it = std::find( 108 current_touch_ids_.begin(), current_touch_ids_.end(), touch_id); 109 110 // Can happen if touch exploration is enabled while fingers were down. 111 if (it == current_touch_ids_.end()) 112 return ui::EVENT_REWRITE_CONTINUE; 113 114 touch_locations_[*it] = location; 115 } 116 VLOG_STATE(); 117 VLOG_EVENT(touch_event); 118 // The rest of the processing depends on what state we're in. 119 switch(state_) { 120 case NO_FINGERS_DOWN: 121 return InNoFingersDown(touch_event, rewritten_event); 122 case SINGLE_TAP_PRESSED: 123 return InSingleTapPressed(touch_event, rewritten_event); 124 case SINGLE_TAP_RELEASED: 125 return InSingleTapReleased(touch_event, rewritten_event); 126 case DOUBLE_TAP_PRESSED: 127 return InDoubleTapPressed(touch_event, rewritten_event); 128 case TOUCH_EXPLORATION: 129 return InTouchExploration(touch_event, rewritten_event); 130 case PASSTHROUGH_MINUS_ONE: 131 return InPassthroughMinusOne(touch_event, rewritten_event); 132 case TOUCH_EXPLORE_SECOND_PRESS: 133 return InTouchExploreSecondPress(touch_event, rewritten_event); 134 } 135 136 NOTREACHED(); 137 return ui::EVENT_REWRITE_CONTINUE; 138 } 139 140 ui::EventRewriteStatus TouchExplorationController::NextDispatchEvent( 141 const ui::Event& last_event, scoped_ptr<ui::Event>* new_event) { 142 NOTREACHED(); 143 return ui::EVENT_REWRITE_CONTINUE; 144 } 145 146 ui::EventRewriteStatus TouchExplorationController::InNoFingersDown( 147 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { 148 const ui::EventType type = event.type(); 149 if (type == ui::ET_TOUCH_PRESSED) { 150 initial_press_.reset(new TouchEvent(event)); 151 tap_timer_.Start(FROM_HERE, 152 gesture_detector_config_.double_tap_timeout, 153 this, 154 &TouchExplorationController::OnTapTimerFired); 155 state_ = SINGLE_TAP_PRESSED; 156 VLOG_STATE(); 157 return ui::EVENT_REWRITE_DISCARD; 158 } 159 160 NOTREACHED(); 161 return ui::EVENT_REWRITE_CONTINUE; 162 } 163 164 ui::EventRewriteStatus TouchExplorationController::InSingleTapPressed( 165 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { 166 const ui::EventType type = event.type(); 167 168 if (type == ui::ET_TOUCH_PRESSED) { 169 // Adding a second finger within the timeout period switches to 170 // passthrough. 171 state_ = PASSTHROUGH_MINUS_ONE; 172 return InPassthroughMinusOne(event, rewritten_event); 173 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { 174 DCHECK_EQ(0U, current_touch_ids_.size()); 175 state_ = SINGLE_TAP_RELEASED; 176 VLOG_STATE(); 177 return EVENT_REWRITE_DISCARD; 178 } else if (type == ui::ET_TOUCH_MOVED) { 179 // If the user moves far enough from the initial touch location (outside 180 // the "slop" region, jump to the touch exploration mode early. 181 // TODO(evy, lisayin): Add gesture recognition here instead - 182 // we should probably jump to gesture mode here if the velocity is 183 // high enough, and touch exploration if the velocity is lower. 184 float delta = (event.location() - initial_press_->location()).Length(); 185 if (delta > gesture_detector_config_.touch_slop) { 186 EnterTouchToMouseMode(); 187 state_ = TOUCH_EXPLORATION; 188 VLOG_STATE(); 189 return InTouchExploration(event, rewritten_event); 190 } 191 192 return EVENT_REWRITE_DISCARD; 193 } 194 NOTREACHED() << "Unexpected event type received."; 195 return ui::EVENT_REWRITE_CONTINUE; 196 } 197 198 ui::EventRewriteStatus TouchExplorationController::InSingleTapReleased( 199 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { 200 const ui::EventType type = event.type(); 201 if (type == ui::ET_TOUCH_PRESSED) { 202 // This is the second tap in a double-tap (or double tap-hold). 203 // Rewrite at location of last touch exploration. 204 // If there is no touch exploration yet, discard instead. 205 if (!last_touch_exploration_) { 206 return ui::EVENT_REWRITE_DISCARD; 207 } 208 rewritten_event->reset( 209 new ui::TouchEvent(ui::ET_TOUCH_PRESSED, 210 last_touch_exploration_->location(), 211 event.touch_id(), 212 event.time_stamp())); 213 (*rewritten_event)->set_flags(event.flags()); 214 state_ = DOUBLE_TAP_PRESSED; 215 VLOG_STATE(); 216 return ui::EVENT_REWRITE_REWRITTEN; 217 } 218 // If the previous press was discarded, we need to also handle its release. 219 if (type == ui::ET_TOUCH_RELEASED && !last_touch_exploration_) { 220 if (current_touch_ids_.size() == 0) { 221 state_ = NO_FINGERS_DOWN; 222 } 223 return ui::EVENT_REWRITE_DISCARD; 224 } 225 NOTREACHED(); 226 return ui::EVENT_REWRITE_CONTINUE; 227 } 228 229 ui::EventRewriteStatus TouchExplorationController::InDoubleTapPressed( 230 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { 231 const ui::EventType type = event.type(); 232 if (type == ui::ET_TOUCH_PRESSED) { 233 return ui::EVENT_REWRITE_DISCARD; 234 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { 235 if (current_touch_ids_.size() != 0) 236 return EVENT_REWRITE_DISCARD; 237 238 // Rewrite release at location of last touch exploration with the same 239 // id as the prevoius press. 240 rewritten_event->reset( 241 new ui::TouchEvent(ui::ET_TOUCH_RELEASED, 242 last_touch_exploration_->location(), 243 initial_press_->touch_id(), 244 event.time_stamp())); 245 (*rewritten_event)->set_flags(event.flags()); 246 ResetToNoFingersDown(); 247 return ui::EVENT_REWRITE_REWRITTEN; 248 } else if (type == ui::ET_TOUCH_MOVED) { 249 return ui::EVENT_REWRITE_DISCARD; 250 } 251 NOTREACHED() << "Unexpected event type received."; 252 return ui::EVENT_REWRITE_CONTINUE; 253 } 254 255 ui::EventRewriteStatus TouchExplorationController::InTouchExploration( 256 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { 257 const ui::EventType type = event.type(); 258 if (type == ui::ET_TOUCH_PRESSED) { 259 // Handle split-tap. 260 initial_press_.reset(new TouchEvent(event)); 261 if (tap_timer_.IsRunning()) 262 tap_timer_.Stop(); 263 rewritten_event->reset( 264 new ui::TouchEvent(ui::ET_TOUCH_PRESSED, 265 last_touch_exploration_->location(), 266 event.touch_id(), 267 event.time_stamp())); 268 (*rewritten_event)->set_flags(event.flags()); 269 state_ = TOUCH_EXPLORE_SECOND_PRESS; 270 VLOG_STATE(); 271 return ui::EVENT_REWRITE_REWRITTEN; 272 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { 273 if (current_touch_ids_.size() == 0) 274 ResetToNoFingersDown(); 275 } else if (type != ui::ET_TOUCH_MOVED) { 276 NOTREACHED() << "Unexpected event type received."; 277 return ui::EVENT_REWRITE_CONTINUE; 278 } 279 280 // Rewrite as a mouse-move event. 281 *rewritten_event = CreateMouseMoveEvent(event.location(), event.flags()); 282 last_touch_exploration_.reset(new TouchEvent(event)); 283 return ui::EVENT_REWRITE_REWRITTEN; 284 } 285 286 ui::EventRewriteStatus TouchExplorationController::InPassthroughMinusOne( 287 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { 288 ui::EventType type = event.type(); 289 gfx::PointF location = event.location_f(); 290 291 if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { 292 if (current_touch_ids_.size() == 0) 293 ResetToNoFingersDown(); 294 295 if (initial_touch_id_passthrough_mapping_ == kTouchIdUnassigned) { 296 if (event.touch_id() == initial_press_->touch_id()) { 297 initial_touch_id_passthrough_mapping_ = kTouchIdNone; 298 } else { 299 // If the only finger now remaining is the first finger, 300 // rewrite as a move to the location of the first finger. 301 initial_touch_id_passthrough_mapping_ = event.touch_id(); 302 rewritten_event->reset( 303 new ui::TouchEvent(ui::ET_TOUCH_MOVED, 304 touch_locations_[initial_press_->touch_id()], 305 initial_touch_id_passthrough_mapping_, 306 event.time_stamp())); 307 (*rewritten_event)->set_flags(event.flags()); 308 return ui::EVENT_REWRITE_REWRITTEN; 309 } 310 } 311 } 312 313 if (event.touch_id() == initial_press_->touch_id()) { 314 if (initial_touch_id_passthrough_mapping_ == kTouchIdNone || 315 initial_touch_id_passthrough_mapping_ == kTouchIdUnassigned) { 316 return ui::EVENT_REWRITE_DISCARD; 317 } 318 319 rewritten_event->reset( 320 new ui::TouchEvent(type, 321 location, 322 initial_touch_id_passthrough_mapping_, 323 event.time_stamp())); 324 (*rewritten_event)->set_flags(event.flags()); 325 return ui::EVENT_REWRITE_REWRITTEN; 326 } 327 328 return ui::EVENT_REWRITE_CONTINUE; 329 } 330 331 ui::EventRewriteStatus TouchExplorationController::InTouchExploreSecondPress( 332 const ui::TouchEvent& event, 333 scoped_ptr<ui::Event>* rewritten_event) { 334 ui::EventType type = event.type(); 335 gfx::PointF location = event.location_f(); 336 if (type == ui::ET_TOUCH_PRESSED) { 337 return ui::EVENT_REWRITE_DISCARD; 338 } else if (type == ui::ET_TOUCH_MOVED) { 339 // Currently this is a discard, but could be something like rotor 340 // in the future. 341 return ui::EVENT_REWRITE_DISCARD; 342 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { 343 // If the touch exploration finger is lifted, there is no option to return 344 // to touch explore anymore. The remaining finger acts as a pending 345 // tap or long tap for the last touch explore location. 346 if (event.touch_id() == last_touch_exploration_->touch_id()){ 347 state_ = DOUBLE_TAP_PRESSED; 348 VLOG_STATE(); 349 return EVENT_REWRITE_DISCARD; 350 } 351 352 // Continue to release the touch only if the touch explore finger is the 353 // only finger remaining. 354 if (current_touch_ids_.size() != 1) 355 return EVENT_REWRITE_DISCARD; 356 357 // Continue to release the touch only if the touch explore finger is the 358 // only finger remaining. 359 if (current_touch_ids_.size() != 1) 360 return EVENT_REWRITE_DISCARD; 361 362 // Rewrite at location of last touch exploration. 363 rewritten_event->reset( 364 new ui::TouchEvent(ui::ET_TOUCH_RELEASED, 365 last_touch_exploration_->location(), 366 initial_press_->touch_id(), 367 event.time_stamp())); 368 (*rewritten_event)->set_flags(event.flags()); 369 state_ = TOUCH_EXPLORATION; 370 VLOG_STATE(); 371 return ui::EVENT_REWRITE_REWRITTEN; 372 } 373 NOTREACHED() << "Unexpected event type received."; 374 return ui::EVENT_REWRITE_CONTINUE; 375 } 376 377 void TouchExplorationController::OnTapTimerFired() { 378 if (state_ != SINGLE_TAP_RELEASED && state_ != SINGLE_TAP_PRESSED) 379 return; 380 381 if (state_ == SINGLE_TAP_RELEASED) { 382 ResetToNoFingersDown(); 383 } else { 384 EnterTouchToMouseMode(); 385 state_ = TOUCH_EXPLORATION; 386 VLOG_STATE(); 387 } 388 389 scoped_ptr<ui::Event> mouse_move = CreateMouseMoveEvent( 390 initial_press_->location(), initial_press_->flags()); 391 DispatchEvent(mouse_move.get()); 392 last_touch_exploration_.reset(new TouchEvent(*initial_press_)); 393 } 394 395 void TouchExplorationController::DispatchEvent(ui::Event* event) { 396 if (event_handler_for_testing_) { 397 event_handler_for_testing_->OnEvent(event); 398 return; 399 } 400 401 ui::EventDispatchDetails result ALLOW_UNUSED = 402 root_window_->GetHost()->dispatcher()->OnEventFromSource(event); 403 } 404 405 scoped_ptr<ui::Event> TouchExplorationController::CreateMouseMoveEvent( 406 const gfx::PointF& location, 407 int flags) { 408 return scoped_ptr<ui::Event>( 409 new ui::MouseEvent( 410 ui::ET_MOUSE_MOVED, 411 location, 412 location, 413 flags | ui::EF_IS_SYNTHESIZED | ui::EF_TOUCH_ACCESSIBILITY, 414 0)); 415 } 416 417 void TouchExplorationController::EnterTouchToMouseMode() { 418 aura::client::CursorClient* cursor_client = 419 aura::client::GetCursorClient(root_window_); 420 if (cursor_client && !cursor_client->IsMouseEventsEnabled()) 421 cursor_client->EnableMouseEvents(); 422 if (cursor_client && cursor_client->IsCursorVisible()) 423 cursor_client->HideCursor(); 424 } 425 426 void TouchExplorationController::ResetToNoFingersDown() { 427 state_ = NO_FINGERS_DOWN; 428 initial_touch_id_passthrough_mapping_ = kTouchIdUnassigned; 429 VLOG_STATE(); 430 if (tap_timer_.IsRunning()) 431 tap_timer_.Stop(); 432 } 433 434 void TouchExplorationController::VlogState(const char* function_name) { 435 if (prev_state_ == state_) 436 return; 437 prev_state_ = state_; 438 const char* state_string = EnumStateToString(state_); 439 VLOG(0) << "\n Function name: " << function_name 440 << "\n State: " << state_string; 441 } 442 443 void TouchExplorationController::VlogEvent(const ui::TouchEvent& touch_event, 444 const char* function_name) { 445 CHECK(touch_event.IsTouchEvent()); 446 if (prev_event_ == NULL || prev_event_->type() != touch_event.type() || 447 prev_event_->touch_id() != touch_event.touch_id()) { 448 const std::string type = EnumEventTypeToString(touch_event.type()); 449 const gfx::PointF& location = touch_event.location_f(); 450 const int touch_id = touch_event.touch_id(); 451 452 VLOG(0) << "\n Function name: " << function_name 453 << "\n Event Type: " << type 454 << "\n Location: " << location.ToString() 455 << "\n Touch ID: " << touch_id 456 << "\n Number of fingers down: " << current_touch_ids_.size(); 457 prev_event_.reset(new TouchEvent(touch_event)); 458 } 459 } 460 461 const char* TouchExplorationController::EnumStateToString(State state) { 462 switch (state) { 463 case NO_FINGERS_DOWN: 464 return "NO_FINGERS_DOWN"; 465 case SINGLE_TAP_PRESSED: 466 return "SINGLE_TAP_PRESSED"; 467 case SINGLE_TAP_RELEASED: 468 return "SINGLE_TAP_RELEASED"; 469 case DOUBLE_TAP_PRESSED: 470 return "DOUBLE_TAP_PRESSED"; 471 case TOUCH_EXPLORATION: 472 return "TOUCH_EXPLORATION"; 473 case PASSTHROUGH_MINUS_ONE: 474 return "PASSTHROUGH_MINUS_ONE"; 475 case TOUCH_EXPLORE_SECOND_PRESS: 476 return "TOUCH_EXPLORE_SECOND_PRESS"; 477 } 478 return "Not a state"; 479 } 480 481 std::string TouchExplorationController::EnumEventTypeToString( 482 ui::EventType type) { 483 // Add more cases later. For now, these are the most frequently seen 484 // event types. 485 switch (type) { 486 case ET_TOUCH_RELEASED: 487 return "ET_TOUCH_RELEASED"; 488 case ET_TOUCH_PRESSED: 489 return "ET_TOUCH_PRESSED"; 490 case ET_TOUCH_MOVED: 491 return "ET_TOUCH_MOVED"; 492 case ET_TOUCH_CANCELLED: 493 return "ET_TOUCH_CANCELLED"; 494 default: 495 return base::IntToString(type); 496 } 497 } 498 499 } // namespace ui 500