diff options
Diffstat (limited to 'ui/wm')
-rw-r--r-- | ui/wm/core/user_activity_detector.cc | 112 | ||||
-rw-r--r-- | ui/wm/core/user_activity_detector.h | 81 | ||||
-rw-r--r-- | ui/wm/core/user_activity_detector_unittest.cc | 200 | ||||
-rw-r--r-- | ui/wm/core/user_activity_observer.h | 35 | ||||
-rw-r--r-- | ui/wm/wm.gyp | 4 |
5 files changed, 432 insertions, 0 deletions
diff --git a/ui/wm/core/user_activity_detector.cc b/ui/wm/core/user_activity_detector.cc new file mode 100644 index 0000000..56bf563 --- /dev/null +++ b/ui/wm/core/user_activity_detector.cc @@ -0,0 +1,112 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/user_activity_detector.h" + +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "ui/events/event.h" +#include "ui/wm/core/user_activity_observer.h" + +namespace wm { + +namespace { + +// Returns a string describing |event|. +std::string GetEventDebugString(const ui::Event* event) { + std::string details = base::StringPrintf( + "type=%d name=%s flags=%d time=%" PRId64, + event->type(), event->name().c_str(), event->flags(), + event->time_stamp().InMilliseconds()); + + if (event->IsKeyEvent()) { + details += base::StringPrintf(" key_code=%d", + static_cast<const ui::KeyEvent*>(event)->key_code()); + } else if (event->IsMouseEvent() || event->IsTouchEvent() || + event->IsGestureEvent()) { + details += base::StringPrintf(" location=%s", + static_cast<const ui::LocatedEvent*>( + event)->location().ToString().c_str()); + } + + return details; +} + +} // namespace + +const int UserActivityDetector::kNotifyIntervalMs = 200; + +// Too low and mouse events generated at the tail end of reconfiguration +// will be reported as user activity and turn the screen back on; too high +// and we'll ignore legitimate activity. +const int UserActivityDetector::kDisplayPowerChangeIgnoreMouseMs = 1000; + +UserActivityDetector::UserActivityDetector() { +} + +UserActivityDetector::~UserActivityDetector() { +} + +bool UserActivityDetector::HasObserver(UserActivityObserver* observer) const { + return observers_.HasObserver(observer); +} + +void UserActivityDetector::AddObserver(UserActivityObserver* observer) { + observers_.AddObserver(observer); +} + +void UserActivityDetector::RemoveObserver(UserActivityObserver* observer) { + observers_.RemoveObserver(observer); +} + +void UserActivityDetector::OnDisplayPowerChanging() { + honor_mouse_events_time_ = GetCurrentTime() + + base::TimeDelta::FromMilliseconds(kDisplayPowerChangeIgnoreMouseMs); +} + +void UserActivityDetector::OnKeyEvent(ui::KeyEvent* event) { + HandleActivity(event); +} + +void UserActivityDetector::OnMouseEvent(ui::MouseEvent* event) { + if (event->flags() & ui::EF_IS_SYNTHESIZED) + return; + if (!honor_mouse_events_time_.is_null() && + GetCurrentTime() < honor_mouse_events_time_) + return; + + HandleActivity(event); +} + +void UserActivityDetector::OnScrollEvent(ui::ScrollEvent* event) { + HandleActivity(event); +} + +void UserActivityDetector::OnTouchEvent(ui::TouchEvent* event) { + HandleActivity(event); +} + +void UserActivityDetector::OnGestureEvent(ui::GestureEvent* event) { + HandleActivity(event); +} + +base::TimeTicks UserActivityDetector::GetCurrentTime() const { + return !now_for_test_.is_null() ? now_for_test_ : base::TimeTicks::Now(); +} + +void UserActivityDetector::HandleActivity(const ui::Event* event) { + base::TimeTicks now = GetCurrentTime(); + last_activity_time_ = now; + if (last_observer_notification_time_.is_null() || + (now - last_observer_notification_time_).InMillisecondsF() >= + kNotifyIntervalMs) { + if (VLOG_IS_ON(1)) + VLOG(1) << "Reporting user activity: " << GetEventDebugString(event); + FOR_EACH_OBSERVER(UserActivityObserver, observers_, OnUserActivity(event)); + last_observer_notification_time_ = now; + } +} + +} // namespace wm diff --git a/ui/wm/core/user_activity_detector.h b/ui/wm/core/user_activity_detector.h new file mode 100644 index 0000000..6a94db1 --- /dev/null +++ b/ui/wm/core/user_activity_detector.h @@ -0,0 +1,81 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_USER_ACTIVITY_DETECTOR_H_ +#define UI_WM_CORE_USER_ACTIVITY_DETECTOR_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/observer_list.h" +#include "base/time/time.h" +#include "ui/events/event_handler.h" +#include "ui/wm/core/wm_core_export.h" + +namespace wm { + +class UserActivityObserver; + +// Watches for input events and notifies observers that the user is active. +class WM_CORE_EXPORT UserActivityDetector : public ui::EventHandler { + public: + // Minimum amount of time between notifications to observers. + static const int kNotifyIntervalMs; + + // Amount of time that mouse events should be ignored after notification + // is received that displays' power states are being changed. + static const int kDisplayPowerChangeIgnoreMouseMs; + + UserActivityDetector(); + virtual ~UserActivityDetector(); + + base::TimeTicks last_activity_time() const { return last_activity_time_; } + + void set_now_for_test(base::TimeTicks now) { now_for_test_ = now; } + + bool HasObserver(UserActivityObserver* observer) const; + void AddObserver(UserActivityObserver* observer); + void RemoveObserver(UserActivityObserver* observer); + + // Called when displays are about to be turned on or off. + void OnDisplayPowerChanging(); + + // ui::EventHandler implementation. + virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE; + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE; + virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE; + virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE; + virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE; + + private: + // Returns |now_for_test_| if set or base::TimeTicks::Now() otherwise. + base::TimeTicks GetCurrentTime() const; + + // Updates |last_activity_time_|. Additionally notifies observers and + // updates |last_observer_notification_time_| if enough time has passed + // since the last notification. + void HandleActivity(const ui::Event* event); + + ObserverList<UserActivityObserver> observers_; + + // Last time at which user activity was observed. + base::TimeTicks last_activity_time_; + + // Last time at which we notified observers that the user was active. + base::TimeTicks last_observer_notification_time_; + + // If set, used when the current time is needed. This can be set by tests to + // simulate the passage of time. + base::TimeTicks now_for_test_; + + // If set, mouse events will be ignored until this time is reached. This + // is to avoid reporting mouse events that occur when displays are turned + // on or off as user activity. + base::TimeTicks honor_mouse_events_time_; + + DISALLOW_COPY_AND_ASSIGN(UserActivityDetector); +}; + +} // namespace wm + +#endif // UI_WM_CORE_USER_ACTIVITY_DETECTOR_H_ diff --git a/ui/wm/core/user_activity_detector_unittest.cc b/ui/wm/core/user_activity_detector_unittest.cc new file mode 100644 index 0000000..8ff14b4 --- /dev/null +++ b/ui/wm/core/user_activity_detector_unittest.cc @@ -0,0 +1,200 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/user_activity_detector.h" + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" +#include "ui/events/keycodes/keyboard_codes.h" +#include "ui/gfx/point.h" +#include "ui/wm/core/user_activity_observer.h" + +namespace wm { + +// Implementation that just counts the number of times we've been told that the +// user is active. +class TestUserActivityObserver : public UserActivityObserver { + public: + TestUserActivityObserver() : num_invocations_(0) {} + + int num_invocations() const { return num_invocations_; } + void reset_stats() { num_invocations_ = 0; } + + // UserActivityObserver implementation. + virtual void OnUserActivity(const ui::Event* event) OVERRIDE { + num_invocations_++; + } + + private: + // Number of times that OnUserActivity() has been called. + int num_invocations_; + + DISALLOW_COPY_AND_ASSIGN(TestUserActivityObserver); +}; + +class UserActivityDetectorTest : public aura::test::AuraTestBase { + public: + UserActivityDetectorTest() {} + virtual ~UserActivityDetectorTest() {} + + virtual void SetUp() OVERRIDE { + AuraTestBase::SetUp(); + observer_.reset(new TestUserActivityObserver); + detector_.reset(new UserActivityDetector); + detector_->AddObserver(observer_.get()); + + now_ = base::TimeTicks::Now(); + detector_->set_now_for_test(now_); + } + + virtual void TearDown() OVERRIDE { + detector_->RemoveObserver(observer_.get()); + AuraTestBase::TearDown(); + } + + protected: + // Move |detector_|'s idea of the current time forward by |delta|. + void AdvanceTime(base::TimeDelta delta) { + now_ += delta; + detector_->set_now_for_test(now_); + } + + scoped_ptr<UserActivityDetector> detector_; + scoped_ptr<TestUserActivityObserver> observer_; + + base::TimeTicks now_; + + private: + DISALLOW_COPY_AND_ASSIGN(UserActivityDetectorTest); +}; + +// Checks that the observer is notified in response to different types of input +// events. +TEST_F(UserActivityDetectorTest, Basic) { + ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE, false); + detector_->OnKeyEvent(&key_event); + EXPECT_FALSE(key_event.handled()); + EXPECT_EQ(now_.ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(1, observer_->num_invocations()); + observer_->reset_stats(); + + base::TimeDelta advance_delta = base::TimeDelta::FromMilliseconds( + UserActivityDetector::kNotifyIntervalMs); + AdvanceTime(advance_delta); + ui::MouseEvent mouse_event( + ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), ui::EF_NONE, ui::EF_NONE); + detector_->OnMouseEvent(&mouse_event); + EXPECT_FALSE(mouse_event.handled()); + EXPECT_EQ(now_.ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(1, observer_->num_invocations()); + observer_->reset_stats(); + + base::TimeTicks time_before_ignore = now_; + + // Temporarily ignore mouse events when displays are turned on or off. + detector_->OnDisplayPowerChanging(); + detector_->OnMouseEvent(&mouse_event); + EXPECT_FALSE(mouse_event.handled()); + EXPECT_EQ(time_before_ignore.ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(0, observer_->num_invocations()); + observer_->reset_stats(); + + const base::TimeDelta kIgnoreMouseTime = + base::TimeDelta::FromMilliseconds( + UserActivityDetector::kDisplayPowerChangeIgnoreMouseMs); + AdvanceTime(kIgnoreMouseTime / 2); + detector_->OnMouseEvent(&mouse_event); + EXPECT_FALSE(mouse_event.handled()); + EXPECT_EQ(time_before_ignore.ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(0, observer_->num_invocations()); + observer_->reset_stats(); + + // After enough time has passed, mouse events should be reported again. + AdvanceTime(std::max(kIgnoreMouseTime, advance_delta)); + detector_->OnMouseEvent(&mouse_event); + EXPECT_FALSE(mouse_event.handled()); + EXPECT_EQ(now_.ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(1, observer_->num_invocations()); + observer_->reset_stats(); + + AdvanceTime(advance_delta); + ui::TouchEvent touch_event( + ui::ET_TOUCH_PRESSED, gfx::Point(), 0, base::TimeDelta()); + detector_->OnTouchEvent(&touch_event); + EXPECT_FALSE(touch_event.handled()); + EXPECT_EQ(now_.ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(1, observer_->num_invocations()); + observer_->reset_stats(); + + AdvanceTime(advance_delta); + ui::GestureEvent gesture_event( + ui::ET_GESTURE_TAP, 0, 0, ui::EF_NONE, + base::TimeDelta::FromMilliseconds(base::Time::Now().ToDoubleT() * 1000), + ui::GestureEventDetails(ui::ET_GESTURE_TAP, 0, 0), 0U); + detector_->OnGestureEvent(&gesture_event); + EXPECT_FALSE(gesture_event.handled()); + EXPECT_EQ(now_.ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(1, observer_->num_invocations()); + observer_->reset_stats(); +} + +// Checks that observers aren't notified too frequently. +TEST_F(UserActivityDetectorTest, RateLimitNotifications) { + // The observer should be notified about a key event. + ui::KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE, false); + detector_->OnKeyEvent(&event); + EXPECT_FALSE(event.handled()); + EXPECT_EQ(1, observer_->num_invocations()); + observer_->reset_stats(); + + // It shouldn't be notified if a second event occurs in the same instant in + // time. + detector_->OnKeyEvent(&event); + EXPECT_FALSE(event.handled()); + EXPECT_EQ(0, observer_->num_invocations()); + observer_->reset_stats(); + + // Advance the time, but not quite enough for another notification to be sent. + AdvanceTime( + base::TimeDelta::FromMilliseconds( + UserActivityDetector::kNotifyIntervalMs - 100)); + detector_->OnKeyEvent(&event); + EXPECT_FALSE(event.handled()); + EXPECT_EQ(0, observer_->num_invocations()); + observer_->reset_stats(); + + // Advance time by the notification interval, definitely moving out of the + // rate limit. This should let us trigger another notification. + AdvanceTime(base::TimeDelta::FromMilliseconds( + UserActivityDetector::kNotifyIntervalMs)); + + detector_->OnKeyEvent(&event); + EXPECT_FALSE(event.handled()); + EXPECT_EQ(1, observer_->num_invocations()); +} + +// Checks that the detector ignores synthetic mouse events. +TEST_F(UserActivityDetectorTest, IgnoreSyntheticMouseEvents) { + ui::MouseEvent mouse_event( + ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), ui::EF_IS_SYNTHESIZED, + ui::EF_NONE); + detector_->OnMouseEvent(&mouse_event); + EXPECT_FALSE(mouse_event.handled()); + EXPECT_EQ(base::TimeTicks().ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(0, observer_->num_invocations()); +} + +} // namespace wm diff --git a/ui/wm/core/user_activity_observer.h b/ui/wm/core/user_activity_observer.h new file mode 100644 index 0000000..2c3b887 --- /dev/null +++ b/ui/wm/core/user_activity_observer.h @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_USER_ACTIVITY_OBSERVER_H_ +#define UI_WM_CORE_USER_ACTIVITY_OBSERVER_H_ + +#include "base/basictypes.h" +#include "ui/wm/core/wm_core_export.h" + +namespace ui { +class Event; +} + +namespace wm { + +// Interface for classes that want to be notified about user activity. +// Implementations should register themselves with UserActivityDetector. +class WM_CORE_EXPORT UserActivityObserver { + public: + // Invoked periodically while the user is active (i.e. generating input + // events). |event| is the event that triggered the notification; it may + // be NULL in some cases (e.g. testing or synthetic invocations). + virtual void OnUserActivity(const ui::Event* event) = 0; + + protected: + UserActivityObserver() {} + virtual ~UserActivityObserver() {} + + DISALLOW_COPY_AND_ASSIGN(UserActivityObserver); +}; + +} // namespace wm + +#endif // UI_WM_CORE_USER_ACTIVITY_OBSERVER_H_ diff --git a/ui/wm/wm.gyp b/ui/wm/wm.gyp index a1960ec..d7b2b7b 100644 --- a/ui/wm/wm.gyp +++ b/ui/wm/wm.gyp @@ -72,6 +72,9 @@ 'core/transient_window_observer.h', 'core/transient_window_stacking_client.cc', 'core/transient_window_stacking_client.h', + 'core/user_activity_detector.cc', + 'core/user_activity_detector.h', + 'core/user_activity_observer.h', 'core/visibility_controller.cc', 'core/visibility_controller.h', 'core/window_animations.cc', @@ -128,6 +131,7 @@ 'core/shadow_controller_unittest.cc', 'core/transient_window_manager_unittest.cc', 'core/transient_window_stacking_client_unittest.cc', + 'core/user_activity_detector_unittest.cc', 'core/visibility_controller_unittest.cc', 'core/window_animations_unittest.cc', 'core/window_util_unittest.cc', |