summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorlisayin@chromium.org <lisayin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-08-14 05:23:38 +0000
committerlisayin@chromium.org <lisayin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-08-14 05:24:53 +0000
commit07f3ea969b262da2da177f4d9092efb0a37dd6ba (patch)
tree37499bd901955b100e5b59f48c31eb91c164d6f0
parent844005f39ef04bd6b8379f182b4db5348348dfc2 (diff)
downloadchromium_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
-rw-r--r--ash/accessibility_delegate.h5
-rw-r--r--ash/ash_touch_exploration_manager_chromeos.cc31
-rw-r--r--ash/ash_touch_exploration_manager_chromeos.h5
-rw-r--r--ash/default_accessibility_delegate.cc3
-rw-r--r--ash/default_accessibility_delegate.h1
-rw-r--r--chrome/browser/browser_resources.grd3
-rw-r--r--chrome/browser/chromeos/accessibility/accessibility_manager.cc13
-rw-r--r--chrome/browser/chromeos/accessibility/accessibility_manager.h5
-rw-r--r--chrome/browser/resources/chromeos/sounds/earcons/enter_screen.wavbin0 -> 102580 bytes
-rw-r--r--chrome/browser/resources/chromeos/sounds/earcons/exit_screen.wavbin0 -> 88244 bytes
-rw-r--r--chrome/browser/resources/chromeos/sounds/earcons/passthrough.wavbin0 -> 88244 bytes
-rw-r--r--chrome/browser/ui/ash/chrome_shell_delegate_chromeos.cc5
-rw-r--r--chrome/browser/ui/ash/chrome_shell_delegate_views.cc3
-rw-r--r--chromeos/audio/chromeos_sounds.h5
-rw-r--r--ui/chromeos/touch_exploration_controller.cc152
-rw-r--r--ui/chromeos/touch_exploration_controller.h45
-rw-r--r--ui/chromeos/touch_exploration_controller_unittest.cc197
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
new file mode 100644
index 0000000..670b88a
--- /dev/null
+++ b/chrome/browser/resources/chromeos/sounds/earcons/enter_screen.wav
Binary files differ
diff --git a/chrome/browser/resources/chromeos/sounds/earcons/exit_screen.wav b/chrome/browser/resources/chromeos/sounds/earcons/exit_screen.wav
new file mode 100644
index 0000000..bea36c1
--- /dev/null
+++ b/chrome/browser/resources/chromeos/sounds/earcons/exit_screen.wav
Binary files differ
diff --git a/chrome/browser/resources/chromeos/sounds/earcons/passthrough.wav b/chrome/browser/resources/chromeos/sounds/earcons/passthrough.wav
new file mode 100644
index 0000000..d499759
--- /dev/null
+++ b/chrome/browser/resources/chromeos/sounds/earcons/passthrough.wav
Binary files differ
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