| Index: ui/chromeos/touch_exploration_controller.cc | 
| diff --git a/ui/chromeos/touch_exploration_controller.cc b/ui/chromeos/touch_exploration_controller.cc | 
| index 287e59fd6d39626498b31342d6cde3915ff92020..5b094072e01a048a865e131baa680330acbec766 100644 | 
| --- a/ui/chromeos/touch_exploration_controller.cc | 
| +++ b/ui/chromeos/touch_exploration_controller.cc | 
| @@ -4,7 +4,6 @@ | 
|  | 
| #include "ui/chromeos/touch_exploration_controller.h" | 
|  | 
| -#include "base/logging.h" | 
| #include "base/strings/string_number_conversions.h" | 
| #include "ui/aura/client/cursor_client.h" | 
| #include "ui/aura/window.h" | 
| @@ -12,6 +11,7 @@ | 
| #include "ui/aura/window_tree_host.h" | 
| #include "ui/events/event.h" | 
| #include "ui/events/event_processor.h" | 
| +#include "ui/gfx/geometry/rect.h" | 
|  | 
| #define VLOG_STATE() if (VLOG_IS_ON(0)) VlogState(__func__) | 
| #define VLOG_EVENT(event) if (VLOG_IS_ON(0)) VlogEvent(event, __func__) | 
| @@ -19,13 +19,37 @@ | 
| namespace ui { | 
|  | 
| namespace { | 
| +int SignOf(float number) { | 
| +  if (number == 0) | 
| +    return 0; | 
| +  return number > 0 ? 1 : -1; | 
| +} | 
| + | 
| +// Delay between adjustment sounds. | 
| +const base::TimeDelta kSoundDelay = base::TimeDelta::FromMilliseconds(150); | 
| + | 
| +// Minimum Volume adjustment. | 
| +const float kMinPercentVolumeChange = 1; | 
| + | 
| +const float kLeavingScreenEdge = 6; | 
| + | 
| +// Swipe/scroll gestures within these bounds (in dips) will change preset | 
| +// settings. | 
| +const float kMaxDistanceFromEdge = 75; | 
| + | 
| +// After a slide gesture has been triggered, if the finger is still within these | 
| +// bounds, the preset settings will still change. | 
| +const float kSlopDistanceFromEdge = kMaxDistanceFromEdge + 40; | 
| + | 
| // In ChromeOS, VKEY_LWIN is synonymous for the search key. | 
| const ui::KeyboardCode kChromeOSSearchKey = ui::VKEY_LWIN; | 
| }  // namespace | 
|  | 
| TouchExplorationController::TouchExplorationController( | 
| -    aura::Window* root_window) | 
| +    aura::Window* root_window, | 
| +    TouchExplorationControllerDelegate* delegate) | 
| : root_window_(root_window), | 
| +      delegate_(delegate), | 
| state_(NO_FINGERS_DOWN), | 
| event_handler_for_testing_(NULL), | 
| gesture_provider_(this), | 
| @@ -67,7 +91,15 @@ bool TouchExplorationController::IsInGestureInProgressStateForTesting() const { | 
| } | 
|  | 
| void TouchExplorationController::SuppressVLOGsForTesting(bool suppress) { | 
| -    VLOG_on_ = !suppress; | 
| +  VLOG_on_ = !suppress; | 
| +} | 
| + | 
| +gfx::Rect TouchExplorationController::BoundsOfRootWindowInDIPForTesting() { | 
| +  return root_window_->GetBoundsInScreen(); | 
| +} | 
| + | 
| +bool TouchExplorationController::IsInSlideGestureStateForTesting() const { | 
| +  return state_ == SLIDE_GESTURE; | 
| } | 
|  | 
| ui::EventRewriteStatus TouchExplorationController::RewriteEvent( | 
| @@ -108,6 +140,16 @@ ui::EventRewriteStatus TouchExplorationController::RewriteEvent( | 
| current_touch_ids_.push_back(touch_id); | 
| touch_locations_.insert(std::pair<int, gfx::PointF>(touch_id, location)); | 
| } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { | 
| +    // If the release is too close to the edge. | 
| +    TouchEvent touch_event = static_cast<const TouchEvent&>(event); | 
| +    if (WithinBoundsOfEdge(touch_event.location(), kLeavingScreenEdge) != | 
| +        SCREEN_CENTER) { | 
| +      if (current_touch_ids_.size() == 0) | 
| +        ResetToNoFingersDown(); | 
| +      else | 
| +        state_ = WAIT_FOR_RELEASE; | 
| +    } | 
| + | 
| std::vector<int>::iterator it = std::find( | 
| current_touch_ids_.begin(), current_touch_ids_.end(), touch_id); | 
|  | 
| @@ -152,6 +194,8 @@ ui::EventRewriteStatus TouchExplorationController::RewriteEvent( | 
| return InPassthrough(touch_event, rewritten_event); | 
| case WAIT_FOR_RELEASE: | 
| return InWaitForRelease(touch_event, rewritten_event); | 
| +    case SLIDE_GESTURE: | 
| +      return InSlideGesture(touch_event, rewritten_event); | 
| } | 
| NOTREACHED(); | 
| return ui::EVENT_REWRITE_CONTINUE; | 
| @@ -222,6 +266,14 @@ ui::EventRewriteStatus TouchExplorationController::InSingleTapPressed( | 
| << "\n Minimum swipe velocity: " | 
| << gesture_detector_config_.minimum_swipe_velocity; | 
|  | 
| +    // Change to slide gesture if the slide occurred at the right edge. | 
| +    int where = WithinBoundsOfEdge(event.location(), kMaxDistanceFromEdge); | 
| +    if ((where | RIGHT_EDGE) == where) { | 
| +      state_ = SLIDE_GESTURE; | 
| +      VLOG_STATE(); | 
| +      return InSlideGesture(event, rewritten_event); | 
| +    } | 
| + | 
| // If the user moves fast enough from the initial touch location, start | 
| // gesture detection. Otherwise, jump to the touch exploration mode early. | 
| if (velocity > gesture_detector_config_.minimum_swipe_velocity) { | 
| @@ -502,6 +554,7 @@ ui::EventRewriteStatus TouchExplorationController::InTouchExploreSecondPress( | 
| initial_press_->touch_id(), | 
| event.time_stamp())); | 
| (*rewritten_event)->set_flags(event.flags()); | 
| +    EnterTouchToMouseMode(); | 
| state_ = TOUCH_EXPLORATION; | 
| EnterTouchToMouseMode(); | 
| VLOG_STATE(); | 
| @@ -528,6 +581,77 @@ ui::EventRewriteStatus TouchExplorationController::InWaitForRelease( | 
| return EVENT_REWRITE_DISCARD; | 
| } | 
|  | 
| +void TouchExplorationController::PlaySoundForTimer() { | 
| +  delegate_->PlayVolumeAdjustSound(); | 
| +} | 
| + | 
| +ui::EventRewriteStatus TouchExplorationController::InSlideGesture( | 
| +    const ui::TouchEvent& event, | 
| +    scoped_ptr<ui::Event>* rewritten_event) { | 
| +  ui::EventType type = event.type(); | 
| +  // If additional fingers are added before a swipe gesture has been registered, | 
| +  // then go into two-to-one passthrough. | 
| +  if (type == ui::ET_TOUCH_PRESSED || | 
| +      event.touch_id() != initial_press_->touch_id()) { | 
| +    if (tap_timer_.IsRunning()) | 
| +      tap_timer_.Stop(); | 
| +    if (sound_timer_.IsRunning()) | 
| +      sound_timer_.Stop(); | 
| +    // Discard any pending gestures. | 
| +    ignore_result(gesture_provider_.GetAndResetPendingGestures()); | 
| +    state_ = TWO_TO_ONE_FINGER; | 
| +    last_two_to_one_.reset(new TouchEvent(event)); | 
| +    rewritten_event->reset(new ui::TouchEvent(ui::ET_TOUCH_PRESSED, | 
| +                                              event.location(), | 
| +                                              event.touch_id(), | 
| +                                              event.time_stamp())); | 
| +    (*rewritten_event)->set_flags(event.flags()); | 
| +    return EVENT_REWRITE_REWRITTEN; | 
| +  } | 
| + | 
| +  VLOG(0) << "Location " << event.location().ToString(); | 
| +  // Allows user to return to the edge to adjust the sound if they have left the | 
| +  // boundaries. | 
| +  int where = WithinBoundsOfEdge(event.location(), kSlopDistanceFromEdge); | 
| +  if (((where | RIGHT_EDGE) != where) && (type != ui::ET_TOUCH_RELEASED)) { | 
| +    if (sound_timer_.IsRunning()) { | 
| +      sound_timer_.Stop(); | 
| +    } | 
| +    return EVENT_REWRITE_DISCARD; | 
| +  } | 
| + | 
| +  // This can occur if the user leaves the screen edge and then returns to it to | 
| +  // continue adjusting the sound. | 
| +  if (!sound_timer_.IsRunning()) { | 
| +    sound_timer_.Start(FROM_HERE, | 
| +                       kSoundDelay, | 
| +                       this, | 
| +                       &ui::TouchExplorationController::PlaySoundForTimer); | 
| +    delegate_->PlayVolumeAdjustSound(); | 
| +  } | 
| +  // The timer should not fire when sliding. | 
| +  if (tap_timer_.IsRunning()) | 
| +    tap_timer_.Stop(); | 
| + | 
| +  // There should not be more than one finger down. | 
| +  DCHECK(current_touch_ids_.size() <= 1); | 
| +  if (type == ui::ET_TOUCH_MOVED) { | 
| +    gesture_provider_.OnTouchEvent(event); | 
| +    gesture_provider_.OnTouchEventAck(false); | 
| +  } | 
| +  if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { | 
| +    gesture_provider_.OnTouchEvent(event); | 
| +    gesture_provider_.OnTouchEventAck(false); | 
| +    ignore_result(gesture_provider_.GetAndResetPendingGestures()); | 
| +    if (current_touch_ids_.size() == 0) | 
| +      ResetToNoFingersDown(); | 
| +    return ui::EVENT_REWRITE_DISCARD; | 
| +  } | 
| + | 
| +  ProcessGestureEvents(); | 
| +  return ui::EVENT_REWRITE_DISCARD; | 
| +} | 
| + | 
| void TouchExplorationController::OnTapTimerFired() { | 
| switch (state_) { | 
| case SINGLE_TAP_RELEASED: | 
| @@ -552,7 +676,7 @@ void TouchExplorationController::OnTapTimerFired() { | 
| CreateMouseMoveEvent(initial_press_->location(), initial_press_->flags()); | 
| DispatchEvent(mouse_move.get()); | 
| last_touch_exploration_.reset(new TouchEvent(*initial_press_)); | 
| - } | 
| +} | 
|  | 
| void TouchExplorationController::DispatchEvent(ui::Event* event) { | 
| if (event_handler_for_testing_) { | 
| @@ -563,12 +687,14 @@ void TouchExplorationController::DispatchEvent(ui::Event* event) { | 
| root_window_->GetHost()->dispatcher()->OnEventFromSource(event); | 
| } | 
|  | 
| -void TouchExplorationController::OnGestureEvent(ui::GestureEvent* gesture) { | 
| +void TouchExplorationController::OnGestureEvent( | 
| +    ui::GestureEvent* gesture) { | 
| CHECK(gesture->IsGestureEvent()); | 
| +  ui::EventType type = gesture->type(); | 
| VLOG(0) << " \n Gesture Triggered: " << gesture->name(); | 
| -  if (gesture->type() == ui::ET_GESTURE_SWIPE) { | 
| -    if (tap_timer_.IsRunning()) | 
| -      tap_timer_.Stop(); | 
| +  if (type == ui::ET_GESTURE_SWIPE && state_ != SLIDE_GESTURE) { | 
| +    VLOG(0) << "Swipe!"; | 
| +    ignore_result(gesture_provider_.GetAndResetPendingGestures()); | 
| OnSwipeEvent(gesture); | 
| return; | 
| } | 
| @@ -581,11 +707,84 @@ void TouchExplorationController::ProcessGestureEvents() { | 
| for (ScopedVector<GestureEvent>::iterator i = gestures->begin(); | 
| i != gestures->end(); | 
| ++i) { | 
| -      OnGestureEvent(*i); | 
| +      if (state_ == SLIDE_GESTURE) | 
| +        SideSlideControl(*i); | 
| +      else | 
| +        OnGestureEvent(*i); | 
| +    } | 
| +  } | 
| +} | 
| + | 
| +void TouchExplorationController::SideSlideControl(ui::GestureEvent* gesture) { | 
| +  ui::EventType type = gesture->type(); | 
| +  if (!gesture->IsScrollGestureEvent()) | 
| +    return; | 
| + | 
| +  gfx::Point location = gesture->location(); | 
| +  VLOG(0) << "Location " << location.ToString(); | 
| +  root_window_->GetHost()->ConvertPointToNativeScreen(&location); | 
| +  if (WithinBoundsOfEdge(location, kSlopDistanceFromEdge) == SCREEN_CENTER) { | 
| +    VLOG(0) << "No more Slide gesture "; | 
| +    // Discard any pending gestures. | 
| +     ignore_result(gesture_provider_.GetAndResetPendingGestures()); | 
| +    if (current_touch_ids_.size() > 1) { | 
| +      state_ = WAIT_FOR_RELEASE; | 
| +    } else if (current_touch_ids_.size() == 0) { | 
| +      ResetToNoFingersDown(); | 
| +    } else { | 
| +      EnterTouchToMouseMode(); | 
| +      state_ = TOUCH_EXPLORATION; | 
| } | 
| +    VLOG_STATE(); | 
| +    return; | 
| +  } | 
| + | 
| +  if (type == ET_GESTURE_SCROLL_BEGIN) { | 
| +    delegate_->PlayVolumeAdjustSound(); | 
| } | 
| + | 
| +  if (type == ET_GESTURE_SCROLL_END) { | 
| +    if (sound_timer_.IsRunning()) | 
| +      sound_timer_.Stop(); | 
| +    delegate_->PlayVolumeAdjustSound(); | 
| +  } | 
| + | 
| +  location = gesture->location(); | 
| +  int where = WithinBoundsOfEdge(location, kSlopDistanceFromEdge); | 
| + | 
| +  // If the user is in the corner of the right side of the screen, the volume | 
| +  // will be automatically set to 100% or muted depending on which corner they | 
| +  // are in. Otherwise, the user will be able to adjust the volume by sliding | 
| +  // their finger along the right side of the screen. Volume is relative to | 
| +  // where they are on the right side of the screen. | 
| +  if ((where | RIGHT_EDGE) != where) | 
| +    return; | 
| + | 
| +  if (where == RIGHT_EDGE) { | 
| +    location = gesture->location(); | 
| +    root_window_->GetHost()->ConvertPointFromNativeScreen(&location); | 
| +    float volume = | 
| +        100 - | 
| +        ((float)location.y() - kMaxDistanceFromEdge) / | 
| +            (root_window_->bounds().bottom() - 2 * kMaxDistanceFromEdge) * 100; | 
| +    VLOG(0) << "\n Volume = " << volume | 
| +            << "\n Location = " << location.ToString() | 
| +            << "\n Bounds = " << root_window_->bounds().right(); | 
| + | 
| +    delegate_->AdjustSound(volume); | 
| +    return; | 
| +  } | 
| +  else if ((where | TOP_EDGE) == where) { | 
| +    delegate_->AdjustSound(100); | 
| +    return; | 
| +  } else if ((where | BOTTOM_EDGE) == where) { | 
| +    delegate_->AdjustSound(0); | 
| +    return; | 
| +  } | 
| +  NOTREACHED() << "Invalid location " << where; | 
| } | 
|  | 
| + | 
| void TouchExplorationController::OnSwipeEvent(ui::GestureEvent* swipe_gesture) { | 
| // A swipe gesture contains details for the direction in which the swipe | 
| // occurred. | 
| @@ -605,6 +804,34 @@ void TouchExplorationController::OnSwipeEvent(ui::GestureEvent* swipe_gesture) { | 
| } | 
| } | 
|  | 
| +int TouchExplorationController::WithinBoundsOfEdge(gfx::Point point, | 
| +                                                   float bounds) { | 
| +  // Since GetBoundsInScreen is in DIPs but p is not, then p needs to be | 
| +  // converted. | 
| +  root_window_->GetHost()->ConvertPointFromNativeScreen(&point); | 
| +  gfx::Rect window= root_window_->GetBoundsInScreen(); | 
| + | 
| +  float left_edge_limit = window.x() + bounds; | 
| +  float right_edge_limit = window.right() - bounds; | 
| +  float top_edge_limit = window.y() + bounds; | 
| +  float bottom_edge_limit = window.bottom() - bounds; | 
| + | 
| +  // Bitwise manipulation in order to determine where on the screen the point | 
| +  // lies. If more than one bit is turned on, then it is a corner where the two | 
| +  // bit/edges intersect. Otherwise, if no bits are turned on, the point must be | 
| +  // in the center of the screen. | 
| +  int result = SCREEN_CENTER; | 
| +  if (point.x() < left_edge_limit) | 
| +    result = result | LEFT_EDGE; | 
| +  if (point.x() > right_edge_limit) | 
| +    result = result | RIGHT_EDGE; | 
| +  if (point.y() < top_edge_limit) | 
| +    result = result | TOP_EDGE; | 
| +  if (point.y() > bottom_edge_limit) | 
| +    result = result | BOTTOM_EDGE; | 
| +  return result; | 
| +} | 
| + | 
| void TouchExplorationController::DispatchShiftSearchKeyEvent( | 
| const ui::KeyboardCode direction) { | 
| // In order to activate the shortcut shift+search+<arrow key> | 
| @@ -654,6 +881,9 @@ void TouchExplorationController::EnterTouchToMouseMode() { | 
| } | 
|  | 
| void TouchExplorationController::ResetToNoFingersDown() { | 
| +  ProcessGestureEvents(); | 
| +  if (sound_timer_.IsRunning()) | 
| +    sound_timer_.Stop(); | 
| state_ = NO_FINGERS_DOWN; | 
| VLOG_STATE(); | 
| if (tap_timer_.IsRunning()) | 
| @@ -725,6 +955,8 @@ const char* TouchExplorationController::EnumStateToString(State state) { | 
| return "PASSTHROUGH"; | 
| case WAIT_FOR_RELEASE: | 
| return "WAIT_FOR_RELEASE"; | 
| +    case SLIDE_GESTURE: | 
| +      return "SLIDE_GESTURE"; | 
| } | 
| return "Not a state"; | 
| } | 
|  |