Home | History | Annotate | Download | only in chromeos
      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