diff options
author | lisayin@chromium.org <lisayin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-08-14 05:23:38 +0000 |
---|---|---|
committer | lisayin@chromium.org <lisayin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-08-14 05:24:53 +0000 |
commit | 07f3ea969b262da2da177f4d9092efb0a37dd6ba (patch) | |
tree | 37499bd901955b100e5b59f48c31eb91c164d6f0 | |
parent | 844005f39ef04bd6b8379f182b4db5348348dfc2 (diff) | |
download | chromium_src-07f3ea969b262da2da177f4d9092efb0a37dd6ba.zip chromium_src-07f3ea969b262da2da177f4d9092efb0a37dd6ba.tar.gz chromium_src-07f3ea969b262da2da177f4d9092efb0a37dd6ba.tar.bz2 |
Implementated corner passthrough when touch exploration
controller is on.
If the user presses and holds the bottom right or left
corner of the screen past a time delay, an earcon will
sound, and then any subsequent fingers added onto the
screen will send touch events as normal (passed through)
as long as one finger is at a corner of the screen. Once
the finger on the corner of the screen has lifted, then
nothing will happen until all the fingers on the screen
have been lifted.
UPDATE: Added earcons (short sound notifications) to
indicate to the user if they have moved their finger off
the screen or onto the screen or if they are in passthrough.
BUG=396310
Review URL: https://codereview.chromium.org/410783002
Cr-Commit-Position: refs/heads/master@{#289468}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@289468 0039d316-1c4b-4281-b951-d872f2087c98
17 files changed, 434 insertions, 39 deletions
diff --git a/ash/accessibility_delegate.h b/ash/accessibility_delegate.h index 7dc2826..410ea7a 100644 --- a/ash/accessibility_delegate.h +++ b/ash/accessibility_delegate.h @@ -92,6 +92,11 @@ class ASH_EXPORT AccessibilityDelegate { // Gets the last accessibility alert that was triggered. virtual AccessibilityAlert GetLastAccessibilityAlert() = 0; + // Plays an earcon. Earcons are brief and distinctive sounds that indicate + // when their mapped event has occurred. The sound key enums can be found in + // chromeos/audio/chromeos_sounds.h. + virtual void PlayEarcon(int sound_key) = 0; + // Initiates play of shutdown sound and returns it's duration. virtual base::TimeDelta PlayShutdownSound() const = 0; }; diff --git a/ash/ash_touch_exploration_manager_chromeos.cc b/ash/ash_touch_exploration_manager_chromeos.cc index f57aedc..e10d2e7 100644 --- a/ash/ash_touch_exploration_manager_chromeos.cc +++ b/ash/ash_touch_exploration_manager_chromeos.cc @@ -37,14 +37,6 @@ void AshTouchExplorationManager::OnAccessibilityModeChanged( UpdateTouchExplorationState(); } -void AshTouchExplorationManager::PlayVolumeAdjustSound() { - if (!VolumeAdjustSoundEnabled()) - return; - if ((!audio_handler_->IsOutputMuted()) || - !(audio_handler_->GetOutputVolumePercent() == 100)) - PlaySystemSoundIfSpokenFeedback(chromeos::SOUND_VOLUME_ADJUST); -} - void AshTouchExplorationManager::SetOutputLevel(int volume) { if (volume > 0) { if (audio_handler_->IsOutputMuted()) { @@ -65,6 +57,29 @@ void AshTouchExplorationManager::SilenceSpokenFeedback() { delegate->SilenceSpokenFeedback(); } +void AshTouchExplorationManager::PlayVolumeAdjustEarcon() { + if (!VolumeAdjustSoundEnabled()) + return; + if (!audio_handler_->IsOutputMuted() && + audio_handler_->GetOutputVolumePercent() != 100) + PlaySystemSoundIfSpokenFeedback(chromeos::SOUND_VOLUME_ADJUST); +} + +void AshTouchExplorationManager::PlayPassthroughEarcon() { + Shell::GetInstance()->accessibility_delegate()->PlayEarcon( + chromeos::SOUND_PASSTHROUGH); +} + +void AshTouchExplorationManager::PlayExitScreenEarcon() { + Shell::GetInstance()->accessibility_delegate()->PlayEarcon( + chromeos::SOUND_EXIT_SCREEN); +} + +void AshTouchExplorationManager::PlayEnterScreenEarcon() { + Shell::GetInstance()->accessibility_delegate()->PlayEarcon( + chromeos::SOUND_ENTER_SCREEN); +} + void AshTouchExplorationManager::UpdateTouchExplorationState() { AccessibilityDelegate* delegate = Shell::GetInstance()->accessibility_delegate(); diff --git a/ash/ash_touch_exploration_manager_chromeos.h b/ash/ash_touch_exploration_manager_chromeos.h index d6ca881..bce2a52 100644 --- a/ash/ash_touch_exploration_manager_chromeos.h +++ b/ash/ash_touch_exploration_manager_chromeos.h @@ -33,9 +33,12 @@ class ASH_EXPORT AshTouchExplorationManager AccessibilityNotificationVisibility notify) OVERRIDE; // TouchExplorationControllerDelegate overrides: - virtual void PlayVolumeAdjustSound() OVERRIDE; virtual void SetOutputLevel(int volume) OVERRIDE; virtual void SilenceSpokenFeedback() OVERRIDE; + virtual void PlayVolumeAdjustEarcon() OVERRIDE; + virtual void PlayPassthroughEarcon() OVERRIDE; + virtual void PlayExitScreenEarcon() OVERRIDE; + virtual void PlayEnterScreenEarcon() OVERRIDE; private: void UpdateTouchExplorationState(); diff --git a/ash/default_accessibility_delegate.cc b/ash/default_accessibility_delegate.cc index ee4f27b..b9ecfa2 100644 --- a/ash/default_accessibility_delegate.cc +++ b/ash/default_accessibility_delegate.cc @@ -110,6 +110,9 @@ AccessibilityAlert DefaultAccessibilityDelegate::GetLastAccessibilityAlert() { return accessibility_alert_; } +void DefaultAccessibilityDelegate::PlayEarcon(int sound_key) { +} + base::TimeDelta DefaultAccessibilityDelegate::PlayShutdownSound() const { return base::TimeDelta(); } diff --git a/ash/default_accessibility_delegate.h b/ash/default_accessibility_delegate.h index 52f8aafd5..70d24e8 100644 --- a/ash/default_accessibility_delegate.h +++ b/ash/default_accessibility_delegate.h @@ -39,6 +39,7 @@ class ASH_EXPORT DefaultAccessibilityDelegate : public AccessibilityDelegate { virtual double GetSavedScreenMagnifierScale() OVERRIDE; virtual void TriggerAccessibilityAlert(AccessibilityAlert alert) OVERRIDE; virtual AccessibilityAlert GetLastAccessibilityAlert() OVERRIDE; + virtual void PlayEarcon(int sound_key) OVERRIDE; virtual base::TimeDelta PlayShutdownSound() const OVERRIDE; private: diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd index 6950253..f352fc8 100644 --- a/chrome/browser/browser_resources.grd +++ b/chrome/browser/browser_resources.grd @@ -410,6 +410,9 @@ <include name="IDR_SOUND_VOLUME_ADJUST_WAV" file="resources\chromeos\sounds\volume_adjust.wav" type="BINDATA" /> <include name="IDR_SOUND_CAMERA_SNAP_WAV" file="resources\chromeos\sounds\camera_snap.wav" type="BINDATA" /> <include name="IDR_SOUND_OBJECT_DELETE_WAV" file="resources\chromeos\sounds\object_delete.wav" type="BINDATA" /> + <include name="IDR_SOUND_PASSTHROUGH_WAV" file="resources\chromeos\sounds\earcons\passthrough.wav" type="BINDATA" /> + <include name="IDR_SOUND_EXIT_SCREEN_WAV" file="resources\chromeos\sounds\earcons\exit_screen.wav" type="BINDATA" /> + <include name="IDR_SOUND_ENTER_SCREEN_WAV" file="resources\chromeos\sounds\earcons\enter_screen.wav" type="BINDATA" /> </if> <if expr="chromeos"> <include name="IDR_ABOUT_POWER_HTML" file="resources\chromeos\power.html" type="BINDATA" /> diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.cc b/chrome/browser/chromeos/accessibility/accessibility_manager.cc index 68b23b2..bdfa489 100644 --- a/chrome/browser/chromeos/accessibility/accessibility_manager.cc +++ b/chrome/browser/chromeos/accessibility/accessibility_manager.cc @@ -222,7 +222,7 @@ void InjectChromeVoxContentScript( for (size_t i = 0; i < content_scripts.size(); i++) { const extensions::UserScript& script = content_scripts[i]; for (size_t j = 0; j < script.js_scripts().size(); ++j) { - const extensions::UserScript::File &file = script.js_scripts()[j]; + const extensions::UserScript::File& file = script.js_scripts()[j]; extensions::ExtensionResource resource = extension->GetResource( file.relative_path()); loader->AppendScript(resource); @@ -373,6 +373,12 @@ AccessibilityManager::AccessibilityManager() manager->Initialize( SOUND_SPOKEN_FEEDBACK_DISABLED, bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_DISABLED_WAV)); + manager->Initialize(SOUND_PASSTHROUGH, + bundle.GetRawDataResource(IDR_SOUND_PASSTHROUGH_WAV)); + manager->Initialize(SOUND_EXIT_SCREEN, + bundle.GetRawDataResource(IDR_SOUND_EXIT_SCREEN_WAV)); + manager->Initialize(SOUND_ENTER_SCREEN, + bundle.GetRawDataResource(IDR_SOUND_ENTER_SCREEN_WAV)); } AccessibilityManager::~AccessibilityManager() { @@ -693,6 +699,11 @@ void AccessibilityManager::OnLocaleChanged() { EnableSpokenFeedback(true, ash::A11Y_NOTIFICATION_NONE); } +void AccessibilityManager::PlayEarcon(int sound_key) { + DCHECK(sound_key < chromeos::SOUND_COUNT); + ash::PlaySystemSoundIfSpokenFeedback(sound_key); +} + bool AccessibilityManager::IsHighContrastEnabled() { return high_contrast_enabled_; } diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.h b/chrome/browser/chromeos/accessibility/accessibility_manager.h index fa3f336..1af7453 100644 --- a/chrome/browser/chromeos/accessibility/accessibility_manager.h +++ b/chrome/browser/chromeos/accessibility/accessibility_manager.h @@ -190,6 +190,11 @@ class AccessibilityManager // Notify accessibility when locale changes occur. void OnLocaleChanged(); + // Plays an earcon. Earcons are brief and distinctive sounds that indicate + // when their mapped event has occurred. The sound key enums can be found in + // chromeos/audio/chromeos_sounds.h. + void PlayEarcon(int sound_key); + protected: AccessibilityManager(); virtual ~AccessibilityManager(); diff --git a/chrome/browser/resources/chromeos/sounds/earcons/enter_screen.wav b/chrome/browser/resources/chromeos/sounds/earcons/enter_screen.wav Binary files differnew file mode 100644 index 0000000..670b88a --- /dev/null +++ b/chrome/browser/resources/chromeos/sounds/earcons/enter_screen.wav diff --git a/chrome/browser/resources/chromeos/sounds/earcons/exit_screen.wav b/chrome/browser/resources/chromeos/sounds/earcons/exit_screen.wav Binary files differnew file mode 100644 index 0000000..bea36c1 --- /dev/null +++ b/chrome/browser/resources/chromeos/sounds/earcons/exit_screen.wav diff --git a/chrome/browser/resources/chromeos/sounds/earcons/passthrough.wav b/chrome/browser/resources/chromeos/sounds/earcons/passthrough.wav Binary files differnew file mode 100644 index 0000000..d499759 --- /dev/null +++ b/chrome/browser/resources/chromeos/sounds/earcons/passthrough.wav diff --git a/chrome/browser/ui/ash/chrome_shell_delegate_chromeos.cc b/chrome/browser/ui/ash/chrome_shell_delegate_chromeos.cc index 6744596..f524049 100644 --- a/chrome/browser/ui/ash/chrome_shell_delegate_chromeos.cc +++ b/chrome/browser/ui/ash/chrome_shell_delegate_chromeos.cc @@ -199,6 +199,11 @@ class AccessibilityDelegateImpl : public ash::AccessibilityDelegate { return ash::A11Y_ALERT_NONE; } + virtual void PlayEarcon(int sound_key) OVERRIDE { + DCHECK(chromeos::AccessibilityManager::Get()); + return chromeos::AccessibilityManager::Get()->PlayEarcon(sound_key); + } + virtual base::TimeDelta PlayShutdownSound() const OVERRIDE { return chromeos::AccessibilityManager::Get()->PlayShutdownSound(); } diff --git a/chrome/browser/ui/ash/chrome_shell_delegate_views.cc b/chrome/browser/ui/ash/chrome_shell_delegate_views.cc index a837010..c9b16cf 100644 --- a/chrome/browser/ui/ash/chrome_shell_delegate_views.cc +++ b/chrome/browser/ui/ash/chrome_shell_delegate_views.cc @@ -153,6 +153,9 @@ class EmptyAccessibilityDelegate : public ash::AccessibilityDelegate { return ash::A11Y_ALERT_NONE; } + virtual void PlayEarcon(int sound_key) OVERRIDE { + } + virtual base::TimeDelta PlayShutdownSound() const OVERRIDE { return base::TimeDelta(); } diff --git a/chromeos/audio/chromeos_sounds.h b/chromeos/audio/chromeos_sounds.h index e159985..81788e6 100644 --- a/chromeos/audio/chromeos_sounds.h +++ b/chromeos/audio/chromeos_sounds.h @@ -19,7 +19,10 @@ enum { SOUND_SPOKEN_FEEDBACK_ENABLED, SOUND_SPOKEN_FEEDBACK_DISABLED, SOUND_VOLUME_ADJUST, - SOUND_COUNT + SOUND_PASSTHROUGH, + SOUND_EXIT_SCREEN, + SOUND_ENTER_SCREEN, + SOUND_COUNT, }; } // namespace chromeos diff --git a/ui/chromeos/touch_exploration_controller.cc b/ui/chromeos/touch_exploration_controller.cc index 96b4e43..8b91009 100644 --- a/ui/chromeos/touch_exploration_controller.cc +++ b/ui/chromeos/touch_exploration_controller.cc @@ -26,6 +26,10 @@ namespace { // Delay between adjustment sounds. const base::TimeDelta kSoundDelay = base::TimeDelta::FromMilliseconds(150); +// Delay before corner passthrough activates. +const base::TimeDelta kCornerPassthroughDelay = + base::TimeDelta::FromMilliseconds(700); + // In ChromeOS, VKEY_LWIN is synonymous for the search key. const ui::KeyboardCode kChromeOSSearchKey = ui::VKEY_LWIN; } // namespace @@ -77,6 +81,13 @@ ui::EventRewriteStatus TouchExplorationController::RewriteEvent( // this event under this new state. } + if (passthrough_timer_.IsRunning() && + event.time_stamp() - initial_press_->time_stamp() > + gesture_detector_config_.longpress_timeout) { + passthrough_timer_.Stop(); + OnPassthroughTimerFired(); + } + const ui::EventType type = touch_event.type(); const gfx::PointF& location = touch_event.location_f(); const int touch_id = touch_event.touch_id(); @@ -116,6 +127,12 @@ ui::EventRewriteStatus TouchExplorationController::RewriteEvent( if ((type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) && FindEdgesWithinBounds(touch_event.location(), kLeavingScreenEdge) != NO_EDGE) { + if (VLOG_on_) + VLOG(0) << "Leaving screen"; + + // Indicates to the user that they are leaving the screen. + delegate_->PlayExitScreenEarcon(); + if (current_touch_ids_.size() == 0) { SET_STATE(NO_FINGERS_DOWN); if (VLOG_on_) { @@ -156,6 +173,8 @@ ui::EventRewriteStatus TouchExplorationController::RewriteEvent( return InGestureInProgress(touch_event, rewritten_event); case TOUCH_EXPLORE_SECOND_PRESS: return InTouchExploreSecondPress(touch_event, rewritten_event); + case CORNER_PASSTHROUGH: + return InCornerPassthrough(touch_event, rewritten_event); case SLIDE_GESTURE: return InSlideGesture(touch_event, rewritten_event); case ONE_FINGER_PASSTHROUGH: @@ -177,28 +196,66 @@ ui::EventRewriteStatus TouchExplorationController::NextDispatchEvent( ui::EventRewriteStatus TouchExplorationController::InNoFingersDown( const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { - ui::EventType type = event.type(); - if (type == ui::ET_TOUCH_PRESSED) { - initial_press_.reset(new TouchEvent(event)); - initial_presses_[event.touch_id()] = event.location(); - last_unused_finger_event_.reset(new TouchEvent(event)); - StartTapTimer(); - SET_STATE(SINGLE_TAP_PRESSED); - return ui::EVENT_REWRITE_DISCARD; + const ui::EventType type = event.type(); + if (type != ui::ET_TOUCH_PRESSED) { + NOTREACHED() << "Unexpected event type received: " << event.name(); + return ui::EVENT_REWRITE_CONTINUE; } - NOTREACHED() << "Unexpected event type received: " << event.name(); - return ui::EVENT_REWRITE_CONTINUE; + + // If the user enters the screen from the edge then send an earcon. + int edge = FindEdgesWithinBounds(event.location(), kLeavingScreenEdge); + if (edge != NO_EDGE) + delegate_->PlayEnterScreenEarcon(); + + int location = FindEdgesWithinBounds(event.location(), kSlopDistanceFromEdge); + // If the press was at a corner, the user might go into corner passthrough + // instead. + bool in_a_bottom_corner = + (BOTTOM_LEFT_CORNER == location) || (BOTTOM_RIGHT_CORNER == location); + if (in_a_bottom_corner) { + passthrough_timer_.Start( + FROM_HERE, + gesture_detector_config_.longpress_timeout, + this, + &TouchExplorationController::OnPassthroughTimerFired); + } + initial_press_.reset(new TouchEvent(event)); + initial_presses_[event.touch_id()] = event.location(); + last_unused_finger_event_.reset(new TouchEvent(event)); + StartTapTimer(); + SET_STATE(SINGLE_TAP_PRESSED); + return ui::EVENT_REWRITE_DISCARD; } ui::EventRewriteStatus TouchExplorationController::InSingleTapPressed( const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { const ui::EventType type = event.type(); + int location = FindEdgesWithinBounds(event.location(), kMaxDistanceFromEdge); + bool in_a_bottom_corner = + (location == BOTTOM_LEFT_CORNER) || (location == BOTTOM_RIGHT_CORNER); + // If the event is from the initial press and the location is no longer in the + // corner, then we are not waiting for a corner passthrough anymore. + if (event.touch_id() == initial_press_->touch_id() && !in_a_bottom_corner) { + if (passthrough_timer_.IsRunning()) { + passthrough_timer_.Stop(); + // Since the long press timer has been running, it is possible that the + // tap timer has timed out before the long press timer has. If the tap + // timer timeout has elapsed, then fire the tap timer. + if (event.time_stamp() - initial_press_->time_stamp() > + gesture_detector_config_.double_tap_timeout) { + OnTapTimerFired(); + } + } + } + if (type == ui::ET_TOUCH_PRESSED) { initial_presses_[event.touch_id()] = event.location(); SET_STATE(TWO_FINGER_TAP); return EVENT_REWRITE_DISCARD; } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { + if (passthrough_timer_.IsRunning()) + passthrough_timer_.Stop(); if (current_touch_ids_.size() == 0 && event.touch_id() == initial_press_->touch_id()) { SET_STATE(SINGLE_TAP_RELEASED); @@ -225,7 +282,7 @@ ui::EventRewriteStatus TouchExplorationController::InSingleTapPressed( } // Change to slide gesture if the slide occurred at the right edge. int edge = FindEdgesWithinBounds(event.location(), kMaxDistanceFromEdge); - if (edge & RIGHT_EDGE) { + if (edge & RIGHT_EDGE && edge != BOTTOM_RIGHT_CORNER) { SET_STATE(SLIDE_GESTURE); return InSlideGesture(event, rewritten_event); } @@ -394,6 +451,37 @@ ui::EventRewriteStatus TouchExplorationController::InGestureInProgress( return ui::EVENT_REWRITE_DISCARD; } +ui::EventRewriteStatus TouchExplorationController::InCornerPassthrough( + const ui::TouchEvent& event, + scoped_ptr<ui::Event>* rewritten_event) { + ui::EventType type = event.type(); + + // If the first finger has left the corner, then exit passthrough. + if (event.touch_id() == initial_press_->touch_id()) { + int edges = FindEdgesWithinBounds(event.location(), kSlopDistanceFromEdge); + bool in_a_bottom_corner = (edges == BOTTOM_LEFT_CORNER) || + (edges == BOTTOM_RIGHT_CORNER); + if (type == ui::ET_TOUCH_MOVED && in_a_bottom_corner) + return ui::EVENT_REWRITE_DISCARD; + + if (current_touch_ids_.size() == 0) { + SET_STATE(NO_FINGERS_DOWN); + return ui::EVENT_REWRITE_DISCARD; + } + SET_STATE(WAIT_FOR_NO_FINGERS); + return ui::EVENT_REWRITE_DISCARD; + } + + rewritten_event->reset(new ui::TouchEvent( + type, event.location(), event.touch_id(), event.time_stamp())); + (*rewritten_event)->set_flags(event.flags()); + + if (current_touch_ids_.size() == 0) + SET_STATE(NO_FINGERS_DOWN); + + return ui::EVENT_REWRITE_REWRITTEN; +} + ui::EventRewriteStatus TouchExplorationController::InOneFingerPassthrough( const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { @@ -465,7 +553,7 @@ ui::EventRewriteStatus TouchExplorationController::InWaitForNoFingers( } void TouchExplorationController::PlaySoundForTimer() { - delegate_->PlayVolumeAdjustSound(); + delegate_->PlayVolumeAdjustEarcon(); } ui::EventRewriteStatus TouchExplorationController::InSlideGesture( @@ -505,7 +593,7 @@ ui::EventRewriteStatus TouchExplorationController::InSlideGesture( kSoundDelay, this, &ui::TouchExplorationController::PlaySoundForTimer); - delegate_->PlayVolumeAdjustSound(); + delegate_->PlayVolumeAdjustEarcon(); } if (current_touch_ids_.size() == 0) { @@ -592,13 +680,11 @@ void TouchExplorationController::OnTapTimerFired() { return; } case SINGLE_TAP_PRESSED: - EnterTouchToMouseMode(); - SET_STATE(TOUCH_EXPLORATION); - break; + if (passthrough_timer_.IsRunning()) + return; case GESTURE_IN_PROGRESS: // If only one finger is down, go into touch exploration. if (current_touch_ids_.size() == 1) { - EnterTouchToMouseMode(); SET_STATE(TOUCH_EXPLORATION); break; } @@ -618,6 +704,31 @@ void TouchExplorationController::OnTapTimerFired() { last_touch_exploration_.reset(new TouchEvent(*initial_press_)); } +void TouchExplorationController::OnPassthroughTimerFired() { + // The passthrough timer will only fire if if the user has held a finger in + // one of the passthrough corners for the duration of the passthrough timeout. + + // Check that initial press isn't null. Also a check that if the initial + // corner press was released, then it should not be in corner passthrough. + if (!initial_press_ || + touch_locations_.find(initial_press_->touch_id()) != + touch_locations_.end()) { + LOG(ERROR) << "No initial press or the initial press has been released."; + } + + gfx::Point location = + ToRoundedPoint(touch_locations_[initial_press_->touch_id()]); + int corner = FindEdgesWithinBounds(location, kSlopDistanceFromEdge); + if (corner != BOTTOM_LEFT_CORNER && corner != BOTTOM_RIGHT_CORNER) + return; + + if (sound_timer_.IsRunning()) + sound_timer_.Stop(); + delegate_->PlayPassthroughEarcon(); + SET_STATE(CORNER_PASSTHROUGH); + return; +} + void TouchExplorationController::DispatchEvent(ui::Event* event) { ui::EventDispatchDetails result ALLOW_UNUSED = root_window_->GetHost()->dispatcher()->OnEventFromSource(event); @@ -658,13 +769,13 @@ void TouchExplorationController::SideSlideControl(ui::GestureEvent* gesture) { ui::EventType type = gesture->type(); if (type == ET_GESTURE_SCROLL_BEGIN) { - delegate_->PlayVolumeAdjustSound(); + delegate_->PlayVolumeAdjustEarcon(); } if (type == ET_GESTURE_SCROLL_END) { if (sound_timer_.IsRunning()) sound_timer_.Stop(); - delegate_->PlayVolumeAdjustSound(); + delegate_->PlayVolumeAdjustEarcon(); } // If the user is in the corner of the right side of the screen, the volume @@ -863,6 +974,7 @@ void TouchExplorationController::SetState(State new_state, case TOUCH_EXPLORATION: case TOUCH_EXPLORE_SECOND_PRESS: case ONE_FINGER_PASSTHROUGH: + case CORNER_PASSTHROUGH: case WAIT_FOR_NO_FINGERS: if (gesture_provider_.get()) gesture_provider_.reset(NULL); @@ -942,6 +1054,8 @@ const char* TouchExplorationController::EnumStateToString(State state) { return "GESTURE_IN_PROGRESS"; case TOUCH_EXPLORE_SECOND_PRESS: return "TOUCH_EXPLORE_SECOND_PRESS"; + case CORNER_PASSTHROUGH: + return "CORNER_PASSTHROUGH"; case SLIDE_GESTURE: return "SLIDE_GESTURE"; case ONE_FINGER_PASSTHROUGH: diff --git a/ui/chromeos/touch_exploration_controller.h b/ui/chromeos/touch_exploration_controller.h index a558480..cc030ec 100644 --- a/ui/chromeos/touch_exploration_controller.h +++ b/ui/chromeos/touch_exploration_controller.h @@ -33,16 +33,28 @@ class TouchExplorationControllerDelegate { public: virtual ~TouchExplorationControllerDelegate() {} - // This function should be called whenever the delegate wants to play a sound - // when the volume adjusts. - virtual void PlayVolumeAdjustSound() = 0; - // Takes an int from 0.0 to 100.0 that indicates the percent the volume // should be set to. virtual void SetOutputLevel(int volume) = 0; // Silences spoken feedback. virtual void SilenceSpokenFeedback() = 0; + + // This function should be called when the volume adjust earcon should be + // played + virtual void PlayVolumeAdjustEarcon() = 0; + + // This function should be called when the passthrough earcon should be + // played. + virtual void PlayPassthroughEarcon() = 0; + + // This function should be called when the exit screen earcon should be + // played. + virtual void PlayExitScreenEarcon() = 0; + + // This function should be called when the enter screen earcon should be + // played. + virtual void PlayEnterScreenEarcon() = 0; }; // TouchExplorationController is used in tandem with "Spoken Feedback" to @@ -142,6 +154,12 @@ class TouchExplorationControllerDelegate { // If the user taps the screen with two fingers and lifts both fingers before // the grace period has passed, spoken feedback is silenced. // +// The user can also enter passthrough by placing a finger on one of the bottom +// corners of the screen until an earcon sounds. After the earcon sounds, the +// user is in passthrough so all subsequent fingers placed on the screen will be +// passed through. Once the finger in the corner has been released, the state +// will switch to wait for one finger. +// // The caller is expected to retain ownership of instances of this class and // destroy them before |root_window| is destroyed. class UI_CHROMEOS_EXPORT TouchExplorationController @@ -176,6 +194,8 @@ class UI_CHROMEOS_EXPORT TouchExplorationController const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event); ui::EventRewriteStatus InTouchExploration( const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event); + ui::EventRewriteStatus InCornerPassthrough( + const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event); ui::EventRewriteStatus InOneFingerPassthrough( const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event); ui::EventRewriteStatus InGestureInProgress( @@ -199,6 +219,13 @@ class UI_CHROMEOS_EXPORT TouchExplorationController void StartTapTimer(); void OnTapTimerFired(); + // This timer is started every timer we get the first press event and the + // finger is in the corner of the screen. + // It fires after the corner passthrough delay elapses. If the + // user is still in the corner by the time this timer fires, all subsequent + // fingers added on the screen will be passed through. + void OnPassthroughTimerFired(); + // Dispatch a new event outside of the event rewriting flow. void DispatchEvent(ui::Event* event); @@ -312,6 +339,11 @@ class UI_CHROMEOS_EXPORT TouchExplorationController // all fingers. ONE_FINGER_PASSTHROUGH, + // If the user has pressed and held down the left corner past long press, + // then as long as they are holding the corner, all subsequent fingers + // registered will be in passthrough. + CORNER_PASSTHROUGH, + // If the user added another finger in SINGLE_TAP_PRESSED, or if the user // has multiple fingers fingers down in any other state between // passthrough, touch exploration, and gestures, they must release @@ -337,6 +369,8 @@ class UI_CHROMEOS_EXPORT TouchExplorationController TOP_EDGE = 1 << 1, LEFT_EDGE = 1 << 2, BOTTOM_EDGE = 1 << 3, + BOTTOM_LEFT_CORNER = LEFT_EDGE | BOTTOM_EDGE, + BOTTOM_RIGHT_CORNER = RIGHT_EDGE | BOTTOM_EDGE, }; // Given a point, if it is within the given bounds of an edge, returns the @@ -397,6 +431,9 @@ class UI_CHROMEOS_EXPORT TouchExplorationController // A timer that fires after the double-tap delay. base::OneShotTimer<TouchExplorationController> tap_timer_; + // A timer that fires to enter passthrough. + base::OneShotTimer<TouchExplorationController> passthrough_timer_; + // A timer to fire an indicating sound when sliding to change volume. base::RepeatingTimer<TouchExplorationController> sound_timer_; diff --git a/ui/chromeos/touch_exploration_controller_unittest.cc b/ui/chromeos/touch_exploration_controller_unittest.cc index afe5b5b..2718c01 100644 --- a/ui/chromeos/touch_exploration_controller_unittest.cc +++ b/ui/chromeos/touch_exploration_controller_unittest.cc @@ -75,21 +75,45 @@ int Factorial(int n) { class MockTouchExplorationControllerDelegate : public ui::TouchExplorationControllerDelegate { public: - virtual void PlayVolumeAdjustSound() OVERRIDE { - ++num_times_adjust_sound_played_; - } virtual void SetOutputLevel(int volume) OVERRIDE { volume_changes_.push_back(volume); } virtual void SilenceSpokenFeedback() OVERRIDE { } + virtual void PlayVolumeAdjustEarcon() OVERRIDE { + ++num_times_adjust_sound_played_; + } + virtual void PlayPassthroughEarcon() OVERRIDE { + ++num_times_passthrough_played_; + } + virtual void PlayExitScreenEarcon() OVERRIDE { + ++num_times_exit_screen_played_; + } + virtual void PlayEnterScreenEarcon() OVERRIDE { + ++num_times_enter_screen_played_; + } const std::vector<float> VolumeChanges() { return volume_changes_; } const size_t NumAdjustSounds() { return num_times_adjust_sound_played_; } + const size_t NumPassthroughSounds() { return num_times_passthrough_played_; } + const size_t NumExitScreenSounds() { return num_times_exit_screen_played_; } + const size_t NumEnterScreenSounds() { + return num_times_enter_screen_played_; + } + + void ResetCountersToZero() { + num_times_adjust_sound_played_ = 0; + num_times_passthrough_played_ = 0; + num_times_exit_screen_played_ = 0; + num_times_enter_screen_played_ = 0; + } private: std::vector<float> volume_changes_; size_t num_times_adjust_sound_played_ = 0; + size_t num_times_passthrough_played_ = 0; + size_t num_times_exit_screen_played_ = 0; + size_t num_times_enter_screen_played_ = 0; }; } // namespace @@ -107,6 +131,12 @@ class TouchExplorationControllerTestApi { touch_exploration_controller_->OnTapTimerFired(); } + void CallPassthroughTimerNowForTesting() { + DCHECK(touch_exploration_controller_->passthrough_timer_.IsRunning()); + touch_exploration_controller_->passthrough_timer_.Stop(); + touch_exploration_controller_->OnPassthroughTimerFired(); + } + void CallTapTimerNowIfRunningForTesting() { if (touch_exploration_controller_->tap_timer_.IsRunning()) { touch_exploration_controller_->tap_timer_.Stop(); @@ -133,6 +163,10 @@ class TouchExplorationControllerTestApi { return touch_exploration_controller_->state_ == touch_exploration_controller_->TWO_FINGER_TAP; } + bool IsInCornerPassthroughStateForTesting() const { + return touch_exploration_controller_->state_ == + touch_exploration_controller_->CORNER_PASSTHROUGH; + } gfx::Rect BoundsOfRootWindowInDIPForTesting() const { return touch_exploration_controller_->root_window_->GetBoundsInScreen(); @@ -240,6 +274,11 @@ class TouchExplorationTest : public aura::test::AuraTestBase { touch_exploration_controller_->CallTapTimerNowForTesting(); } + void AdvanceSimulatedTimePastPassthroughDelay() { + simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(1000)); + touch_exploration_controller_->CallPassthroughTimerNowForTesting(); + } + void AdvanceSimulatedTimePastPotentialTapDelay() { simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(1000)); touch_exploration_controller_->CallTapTimerNowIfRunningForTesting(); @@ -273,6 +312,49 @@ class TouchExplorationTest : public aura::test::AuraTestBase { EXPECT_TRUE(IsInTouchToMouseMode()); } + // Checks that Corner Passthrough is working. Assumes that corner is the + // bottom left corner or the bottom right corner. + void AssertCornerPassthroughWorking(gfx::Point corner) { + ASSERT_EQ(0U, delegate_.NumPassthroughSounds()); + + ui::TouchEvent first_press(ui::ET_TOUCH_PRESSED, corner, 0, Now()); + generator_->Dispatch(&first_press); + + AdvanceSimulatedTimePastPassthroughDelay(); + EXPECT_FALSE(IsInGestureInProgressState()); + EXPECT_FALSE(IsInSlideGestureState()); + EXPECT_FALSE(IsInTouchToMouseMode()); + EXPECT_TRUE(IsInCornerPassthroughState()); + + gfx::Rect window = BoundsOfRootWindowInDIP(); + // The following events should be passed through. + gfx::Point passthrough(window.right() / 2, window.bottom() / 2); + ui::TouchEvent passthrough_press( + ui::ET_TOUCH_PRESSED, passthrough, 1, Now()); + ASSERT_EQ(1U, delegate_.NumPassthroughSounds()); + generator_->Dispatch(&passthrough_press); + generator_->ReleaseTouchId(1); + generator_->PressTouchId(1); + EXPECT_FALSE(IsInGestureInProgressState()); + EXPECT_FALSE(IsInSlideGestureState()); + EXPECT_TRUE(IsInCornerPassthroughState()); + + std::vector<ui::LocatedEvent*> captured_events = GetCapturedLocatedEvents(); + ASSERT_EQ(3U, captured_events.size()); + EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[0]->type()); + EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[1]->type()); + EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[2]->type()); + generator_->ReleaseTouchId(1); + ClearCapturedEvents(); + + generator_->ReleaseTouchId(0); + captured_events = GetCapturedLocatedEvents(); + ASSERT_EQ(0U, captured_events.size()); + EXPECT_FALSE(IsInTouchToMouseMode()); + EXPECT_FALSE(IsInCornerPassthroughState()); + ClearCapturedEvents(); + } + bool IsInTouchToMouseMode() { aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(root_window()); @@ -298,15 +380,20 @@ class TouchExplorationTest : public aura::test::AuraTestBase { return touch_exploration_controller_->IsInTwoFingerTapStateForTesting(); } + bool IsInCornerPassthroughState() { + return touch_exploration_controller_ + ->IsInCornerPassthroughStateForTesting(); + } + gfx::Rect BoundsOfRootWindowInDIP() { return touch_exploration_controller_->BoundsOfRootWindowInDIPForTesting(); } - float GetMaxDistanceFromEdge() const{ + float GetMaxDistanceFromEdge() const { return touch_exploration_controller_->GetMaxDistanceFromEdge(); } - float GetSlopDistanceFromEdge() const{ + float GetSlopDistanceFromEdge() const { return touch_exploration_controller_->GetSlopDistanceFromEdge(); } @@ -1696,4 +1783,104 @@ TEST_F(TouchExplorationTest, TwoFingerTapAndMoveSecondFinger) { EXPECT_FALSE(IsInTwoFingerTapState()); } +// Corner passthrough should turn on if the user first holds down on either the +// right or left corner past a delay and then places a finger anywhere else on +// the screen. +TEST_F(TouchExplorationTest, ActivateLeftCornerPassthrough) { + SwitchTouchExplorationMode(true); + + gfx::Rect window = BoundsOfRootWindowInDIP(); + gfx::Point left_corner(10, window.bottom() - GetMaxDistanceFromEdge() / 2); + AssertCornerPassthroughWorking(left_corner); +} + +TEST_F(TouchExplorationTest, ActivateRightCornerPassthrough) { + SwitchTouchExplorationMode(true); + + gfx::Rect window = BoundsOfRootWindowInDIP(); + gfx::Point right_corner(window.right() - GetMaxDistanceFromEdge() / 2, + window.bottom() - GetMaxDistanceFromEdge() / 2); + AssertCornerPassthroughWorking(right_corner); +} + +// Earcons should play if the user slides off the screen or enters the screen +// from the edge. +TEST_F(TouchExplorationTest, EnterEarconPlays) { + SwitchTouchExplorationMode(true); + + gfx::Rect window = BoundsOfRootWindowInDIP(); + + gfx::Point upper_left_corner(0, 0); + gfx::Point upper_right_corner(window.right(), 0); + gfx::Point lower_left_corner(0, window.bottom()); + gfx::Point lower_right_corner(window.right(), window.bottom()); + gfx::Point left_edge(0, 30); + gfx::Point right_edge(window.right(), 30); + gfx::Point top_edge(30, 0); + gfx::Point bottom_edge(30, window.bottom()); + + std::vector<gfx::Point> locations; + locations.push_back(upper_left_corner); + locations.push_back(upper_right_corner); + locations.push_back(lower_left_corner); + locations.push_back(lower_right_corner); + locations.push_back(left_edge); + locations.push_back(right_edge); + locations.push_back(top_edge); + locations.push_back(bottom_edge); + + for (std::vector<gfx::Point>::const_iterator point = locations.begin(); + point != locations.end(); + ++point) { + ui::TouchEvent touch_event(ui::ET_TOUCH_PRESSED, *point, 1, Now()); + + generator_->Dispatch(&touch_event); + ASSERT_EQ(1U, delegate_.NumEnterScreenSounds()); + generator_->ReleaseTouchId(1); + delegate_.ResetCountersToZero(); + } +} + +TEST_F(TouchExplorationTest, ExitEarconPlays) { + SwitchTouchExplorationMode(true); + + // On the device, it cannot actually tell if the finger has left the screen or + // not. If the finger has left the screen, it reads it as a release that + // occurred very close to the edge of the screen even if the finger is still + // technically touching the moniter. To simulate this, a release that occurs + // close to the edge is dispatched. + gfx::Point initial_press(100, 200); + gfx::Rect window = BoundsOfRootWindowInDIP(); + + gfx::Point upper_left_corner(0, 0); + gfx::Point upper_right_corner(window.right(), 0); + gfx::Point lower_left_corner(0, window.bottom()); + gfx::Point lower_right_corner(window.right(), window.bottom()); + gfx::Point left_edge(0, 30); + gfx::Point right_edge(window.right(), 30); + gfx::Point top_edge(30, 0); + gfx::Point bottom_edge(30, window.bottom()); + + std::vector<gfx::Point> locations; + locations.push_back(upper_left_corner); + locations.push_back(upper_right_corner); + locations.push_back(lower_left_corner); + locations.push_back(lower_right_corner); + locations.push_back(left_edge); + locations.push_back(right_edge); + locations.push_back(top_edge); + locations.push_back(bottom_edge); + + for (std::vector<gfx::Point>::const_iterator point = locations.begin(); + point != locations.end(); + ++point) { + generator_->PressTouch(); + generator_->MoveTouch(initial_press); + generator_->MoveTouch(*point); + generator_->ReleaseTouch(); + ASSERT_EQ(1U, delegate_.NumExitScreenSounds()); + delegate_.ResetCountersToZero(); + } +} + } // namespace ui |