diff options
author | derat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-18 22:26:40 +0000 |
---|---|---|
committer | derat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-18 22:26:40 +0000 |
commit | a3102766e725636ad23ad99faa1e8a481a1129e7 (patch) | |
tree | cea797379b158ff00fb8d4b2e86c67046b548946 /ash | |
parent | 8431abd52eb3f7cc0eb9812933fe3eb5e83def45 (diff) | |
download | chromium_src-a3102766e725636ad23ad99faa1e8a481a1129e7.zip chromium_src-a3102766e725636ad23ad99faa1e8a481a1129e7.tar.gz chromium_src-a3102766e725636ad23ad99faa1e8a481a1129e7.tar.bz2 |
aura/chromeos: Avoid suspending while video is playing.
This adds an ash::VideoDetector class that watches for layer
updates and attempts to detect the playback of video. A
Chrome OS-specific observer updates a _CHROME_VIDEO_TIME
property on the X root window, which is used by the power
manager to defer screen dimming or other power management
features. This matches the implementation currently in use
on Chrome OS, with Chrome taking the X window manager's role
for detecting video and setting the property.
BUG=110114
TEST=added; also manually checked that the property is updated
Review URL: http://codereview.chromium.org/9249004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@118171 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ash')
-rw-r--r-- | ash/ash.gyp | 3 | ||||
-rw-r--r-- | ash/shell.cc | 2 | ||||
-rw-r--r-- | ash/shell.h | 5 | ||||
-rw-r--r-- | ash/wm/power_button_controller.h | 3 | ||||
-rw-r--r-- | ash/wm/video_detector.cc | 111 | ||||
-rw-r--r-- | ash/wm/video_detector.h | 100 | ||||
-rw-r--r-- | ash/wm/video_detector_unittest.cc | 196 |
7 files changed, 419 insertions, 1 deletions
diff --git a/ash/ash.gyp b/ash/ash.gyp index 380b9db..c03b884 100644 --- a/ash/ash.gyp +++ b/ash/ash.gyp @@ -147,6 +147,8 @@ 'wm/toplevel_layout_manager.h', 'wm/toplevel_window_event_filter.cc', 'wm/toplevel_window_event_filter.h', + 'wm/video_detector.cc', + 'wm/video_detector.h', 'wm/window_cycle_controller.cc', 'wm/window_cycle_controller.h', 'wm/window_cycle_list.cc', @@ -221,6 +223,7 @@ 'wm/shelf_layout_manager_unittest.cc', 'wm/toplevel_layout_manager_unittest.cc', 'wm/toplevel_window_event_filter_unittest.cc', + 'wm/video_detector_unittest.cc', 'wm/window_cycle_controller_unittest.cc', 'wm/window_modality_controller_unittest.cc', 'wm/workspace_controller_unittest.cc', diff --git a/ash/shell.cc b/ash/shell.cc index ecb1783..3f5e218 100644 --- a/ash/shell.cc +++ b/ash/shell.cc @@ -36,6 +36,7 @@ #include "ash/wm/window_modality_controller.h" #include "ash/wm/window_util.h" #include "ash/wm/workspace_controller.h" +#include "ash/wm/video_detector.h" #include "base/bind.h" #include "base/command_line.h" #include "ui/aura/client/aura_constants.h" @@ -265,6 +266,7 @@ void Shell::Init() { drag_drop_controller_.reset(new internal::DragDropController); power_button_controller_.reset(new PowerButtonController); + video_detector_.reset(new VideoDetector); window_cycle_controller_.reset(new WindowCycleController); } diff --git a/ash/shell.h b/ash/shell.h index e9251ed..66ad2ce 100644 --- a/ash/shell.h +++ b/ash/shell.h @@ -38,6 +38,7 @@ class AcceleratorController; class Launcher; class PowerButtonController; class ShellDelegate; +class VideoDetector; class WindowCycleController; namespace internal { @@ -106,6 +107,9 @@ class ASH_EXPORT Shell { PowerButtonController* power_button_controller() { return power_button_controller_.get(); } + VideoDetector* video_detector() { + return video_detector_.get(); + } WindowCycleController* window_cycle_controller() { return window_cycle_controller_.get(); } @@ -173,6 +177,7 @@ class ASH_EXPORT Shell { scoped_ptr<internal::ShadowController> shadow_controller_; scoped_ptr<internal::TooltipController> tooltip_controller_; scoped_ptr<PowerButtonController> power_button_controller_; + scoped_ptr<VideoDetector> video_detector_; scoped_ptr<WindowCycleController> window_cycle_controller_; // An event filter that pre-handles all key events to send them to an IME. diff --git a/ash/wm/power_button_controller.h b/ash/wm/power_button_controller.h index 3bac34e..00a1507 100644 --- a/ash/wm/power_button_controller.h +++ b/ash/wm/power_button_controller.h @@ -64,7 +64,8 @@ class ASH_EXPORT PowerButtonController : public aura::RootWindowObserver { // Helper class used by tests to access internal state. class ASH_EXPORT TestApi { public: - TestApi(PowerButtonController* controller) : controller_(controller) {} + explicit TestApi(PowerButtonController* controller) + : controller_(controller) {} bool lock_timer_is_running() const { return controller_->lock_timer_.IsRunning(); diff --git a/ash/wm/video_detector.cc b/ash/wm/video_detector.cc new file mode 100644 index 0000000..8be1e4f --- /dev/null +++ b/ash/wm/video_detector.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2012 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 "ash/wm/video_detector.h" + +#include "ui/aura/root_window.h" +#include "ui/aura/window.h" +#include "ui/gfx/rect.h" + +namespace ash { + +const int VideoDetector::kMinUpdateWidth = 300; +const int VideoDetector::kMinUpdateHeight = 225; +const int VideoDetector::kMinFramesPerSecond = 15; +const double VideoDetector::kNotifyIntervalSec = 1.0; + +// Stores information about updates to a window and determines whether it's +// likely that a video is playing in it. +class VideoDetector::WindowInfo { + public: + WindowInfo() : num_video_updates_in_second_(0) {} + + // Handles an update within a window, returning true if this update made us + // believe that a video is playing in the window. true is returned at most + // once per second. + bool RecordUpdateAndCheckForVideo(const gfx::Rect& region, + base::TimeTicks now) { + if (region.width() < kMinUpdateWidth || region.height() < kMinUpdateHeight) + return false; + + if (second_start_time_.is_null() || + (now - second_start_time_).InSecondsF() >= 1.0) { + second_start_time_ = now; + num_video_updates_in_second_ = 0; + } + num_video_updates_in_second_++; + + return num_video_updates_in_second_ == kMinFramesPerSecond; + } + + private: + // Number of video-sized updates that we've seen in the second starting at + // |second_start_time_|. (Keeping a rolling window is overkill here.) + int num_video_updates_in_second_; + + base::TimeTicks second_start_time_; + + DISALLOW_COPY_AND_ASSIGN(WindowInfo); +}; + +VideoDetector::VideoDetector() { + aura::RootWindow::GetInstance()->AddRootWindowObserver(this); +} + +VideoDetector::~VideoDetector() { + aura::RootWindow::GetInstance()->RemoveRootWindowObserver(this); + for (WindowInfoMap::const_iterator it = window_infos_.begin(); + it != window_infos_.end(); ++it) { + aura::Window* window = it->first; + window->RemoveObserver(this); + } +} + +void VideoDetector::AddObserver(VideoDetectorObserver* observer) { + observers_.AddObserver(observer); +} + +void VideoDetector::RemoveObserver(VideoDetectorObserver* observer) { + observers_.RemoveObserver(observer); +} + +void VideoDetector::OnWindowInitialized(aura::Window* window) { + window->AddObserver(this); +} + +void VideoDetector::OnWindowPaintScheduled(aura::Window* window, + const gfx::Rect& region) { + linked_ptr<WindowInfo>& info = window_infos_[window]; + if (!info.get()) + info.reset(new WindowInfo); + + base::TimeTicks now = + !now_for_test_.is_null() ? now_for_test_ : base::TimeTicks::Now(); + if (info->RecordUpdateAndCheckForVideo(region, now)) + MaybeNotifyObservers(window, now); +} + +void VideoDetector::OnWindowDestroyed(aura::Window* window) { + window_infos_.erase(window); +} + +void VideoDetector::MaybeNotifyObservers(aura::Window* window, + base::TimeTicks now) { + if (!last_observer_notification_time_.is_null() && + (now - last_observer_notification_time_).InSecondsF() < + kNotifyIntervalSec) + return; + + if (!window->IsVisible()) + return; + + gfx::Rect root_bounds = aura::RootWindow::GetInstance()->bounds(); + if (!window->GetScreenBounds().Intersects(root_bounds)) + return; + + FOR_EACH_OBSERVER(VideoDetectorObserver, observers_, OnVideoDetected()); + last_observer_notification_time_ = now; +} + +} // namespace ash diff --git a/ash/wm/video_detector.h b/ash/wm/video_detector.h new file mode 100644 index 0000000..d6778b6 --- /dev/null +++ b/ash/wm/video_detector.h @@ -0,0 +1,100 @@ +// Copyright (c) 2012 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 ASH_WM_VIDEO_DETECTOR_H_ +#define ASH_WM_VIDEO_DETECTOR_H_ +#pragma once + +#include <map> + +#include "ash/ash_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/linked_ptr.h" +#include "base/observer_list.h" +#include "base/time.h" +#include "ui/aura/root_window_observer.h" +#include "ui/aura/window_observer.h" + +namespace aura { +class Window; +} + +namespace gfx { +class Rect; +} + +namespace ash { + +class ASH_EXPORT VideoDetectorObserver { + public: + // Invoked periodically while a video is being played onscreen. + virtual void OnVideoDetected() = 0; + + protected: + virtual ~VideoDetectorObserver() {} +}; + +// Watches for updates to windows and tries to detect when a video is playing. +// We err on the side of false positives and can be fooled by things like +// continuous scrolling of a page. +class ASH_EXPORT VideoDetector : public aura::RootWindowObserver, + public aura::WindowObserver { + public: + // Minimum dimensions in pixels that a window update must have to be + // considered a potential video frame. + static const int kMinUpdateWidth; + static const int kMinUpdateHeight; + + // Number of video-sized updates that we must see within a second in a window + // before we assume that a video is playing. + static const int kMinFramesPerSecond; + + // Minimum amount of time between notifications to observers that a video is + // playing. + static const double kNotifyIntervalSec; + + VideoDetector(); + virtual ~VideoDetector(); + + void set_now_for_test(base::TimeTicks now) { now_for_test_ = now; } + + void AddObserver(VideoDetectorObserver* observer); + void RemoveObserver(VideoDetectorObserver* observer); + + // RootWindowObserver overrides. + virtual void OnWindowInitialized(aura::Window* window) OVERRIDE; + + // WindowObserver overrides. + virtual void OnWindowPaintScheduled(aura::Window* window, + const gfx::Rect& region) OVERRIDE; + virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE; + + private: + class WindowInfo; + typedef std::map<aura::Window*, linked_ptr<WindowInfo> > WindowInfoMap; + + // Possibly notifies observers in response to detection of a video in + // |window|. Notifications are rate-limited and don't get sent if the window + // is invisible or offscreen. + void MaybeNotifyObservers(aura::Window* window, base::TimeTicks now); + + // Maps from a window that we're tracking to information about it. + WindowInfoMap window_infos_; + + ObserverList<VideoDetectorObserver> observers_; + + // Last time at which we notified observers that a video was playing. + 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_; + + DISALLOW_COPY_AND_ASSIGN(VideoDetector); +}; + +} // namespace ash + +#endif // ASH_WM_VIDEO_DETECTOR_H_ diff --git a/ash/wm/video_detector_unittest.cc b/ash/wm/video_detector_unittest.cc new file mode 100644 index 0000000..0b82208 --- /dev/null +++ b/ash/wm/video_detector_unittest.cc @@ -0,0 +1,196 @@ +// Copyright (c) 2012 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 "ash/wm/video_detector.h" + +#include "ash/shell.h" +#include "ash/test/aura_shell_test_base.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/time.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/aura/client/window_types.h" +#include "ui/aura/root_window.h" +#include "ui/aura/test/test_windows.h" +#include "ui/aura/window.h" +#include "ui/gfx/rect.h" + +namespace ash { +namespace test { + +// Implementation that just counts the number of times we've been told that a +// video is playing. +class TestVideoDetectorObserver : public VideoDetectorObserver { + public: + TestVideoDetectorObserver() : num_invocations_(0) {} + + int num_invocations() const { return num_invocations_; } + void reset_stats() { num_invocations_ = 0; } + + // VideoDetectorObserver implementation. + virtual void OnVideoDetected() OVERRIDE { num_invocations_++; } + + private: + // Number of times that OnVideoDetected() has been called. + int num_invocations_; + + DISALLOW_COPY_AND_ASSIGN(TestVideoDetectorObserver); +}; + +class VideoDetectorTest : public AuraShellTestBase { + public: + VideoDetectorTest() {} + virtual ~VideoDetectorTest() {} + + void SetUp() OVERRIDE { + AuraShellTestBase::SetUp(); + observer_.reset(new TestVideoDetectorObserver); + detector_ = Shell::GetInstance()->video_detector(); + detector_->AddObserver(observer_.get()); + + now_ = base::TimeTicks::Now(); + detector_->set_now_for_test(now_); + } + + 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_); + } + + VideoDetector* detector_; // not owned + + scoped_ptr<TestVideoDetectorObserver> observer_; + + base::TimeTicks now_; + + private: + DISALLOW_COPY_AND_ASSIGN(VideoDetectorTest); +}; + +TEST_F(VideoDetectorTest, Basic) { + gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768)); + scoped_ptr<aura::Window> window( + aura::test::CreateTestWindow(SK_ColorRED, 12345, window_bounds, NULL)); + + // Send enough updates, but make them be too small to trigger detection. + gfx::Rect update_region( + gfx::Point(), + gfx::Size(VideoDetector::kMinUpdateWidth - 1, + VideoDetector::kMinUpdateHeight)); + for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) + detector_->OnWindowPaintScheduled(window.get(), update_region); + EXPECT_EQ(0, observer_->num_invocations()); + + // Send not-quite-enough adaquately-sized updates. + observer_->reset_stats(); + AdvanceTime(base::TimeDelta::FromSeconds(2)); + update_region.set_size( + gfx::Size(VideoDetector::kMinUpdateWidth, + VideoDetector::kMinUpdateHeight)); + for (int i = 0; i < VideoDetector::kMinFramesPerSecond - 1; ++i) + detector_->OnWindowPaintScheduled(window.get(), update_region); + EXPECT_EQ(0, observer_->num_invocations()); + + // We should get notified after the next update, but not in response to + // additional updates. + detector_->OnWindowPaintScheduled(window.get(), update_region); + EXPECT_EQ(1, observer_->num_invocations()); + detector_->OnWindowPaintScheduled(window.get(), update_region); + EXPECT_EQ(1, observer_->num_invocations()); + + // Spread out the frames over two seconds; we shouldn't detect video. + observer_->reset_stats(); + AdvanceTime(base::TimeDelta::FromSeconds(2)); + for (int i = 0; i < VideoDetector::kMinFramesPerSecond - 1; ++i) + detector_->OnWindowPaintScheduled(window.get(), update_region); + AdvanceTime(base::TimeDelta::FromSeconds(1)); + for (int i = 0; i < VideoDetector::kMinFramesPerSecond - 1; ++i) + detector_->OnWindowPaintScheduled(window.get(), update_region); + EXPECT_EQ(0, observer_->num_invocations()); +} + +TEST_F(VideoDetectorTest, WindowNotVisible) { + gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768)); + scoped_ptr<aura::Window> window( + aura::test::CreateTestWindow(SK_ColorRED, 12345, window_bounds, NULL)); + gfx::Rect update_region( + gfx::Point(), + gfx::Size(VideoDetector::kMinUpdateWidth, + VideoDetector::kMinUpdateHeight)); + + // We shouldn't report video that's played in a hidden window. + window->Hide(); + for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) + detector_->OnWindowPaintScheduled(window.get(), update_region); + EXPECT_EQ(0, observer_->num_invocations()); + + // Make the window visible and send more updates. + observer_->reset_stats(); + AdvanceTime(base::TimeDelta::FromSeconds(2)); + window->Show(); + for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) + detector_->OnWindowPaintScheduled(window.get(), update_region); + EXPECT_EQ(1, observer_->num_invocations()); + + // We also shouldn't report video in a window that's fully offscreen. + observer_->reset_stats(); + AdvanceTime(base::TimeDelta::FromSeconds(2)); + gfx::Rect offscreen_bounds( + gfx::Point(aura::RootWindow::GetInstance()->bounds().width(), 0), + window_bounds.size()); + window->SetBounds(offscreen_bounds); + ASSERT_EQ(offscreen_bounds, window->bounds()); + for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) + detector_->OnWindowPaintScheduled(window.get(), update_region); + EXPECT_EQ(0, observer_->num_invocations()); +} + +TEST_F(VideoDetectorTest, MultipleWindows) { + // Create two windows. + gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768)); + scoped_ptr<aura::Window> window1( + aura::test::CreateTestWindow(SK_ColorRED, 12345, window_bounds, NULL)); + scoped_ptr<aura::Window> window2( + aura::test::CreateTestWindow(SK_ColorBLUE, 23456, window_bounds, NULL)); + + // Even if there's video playing in both, the observer should only receive a + // single notification. + gfx::Rect update_region( + gfx::Point(), + gfx::Size(VideoDetector::kMinUpdateWidth, + VideoDetector::kMinUpdateHeight)); + for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) + detector_->OnWindowPaintScheduled(window1.get(), update_region); + for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) + detector_->OnWindowPaintScheduled(window2.get(), update_region); + EXPECT_EQ(1, observer_->num_invocations()); +} + +// Test that the observer receives repeated notifications. +TEST_F(VideoDetectorTest, RepeatedNotifications) { + gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768)); + scoped_ptr<aura::Window> window( + aura::test::CreateTestWindow(SK_ColorRED, 12345, window_bounds, NULL)); + + gfx::Rect update_region( + gfx::Point(), + gfx::Size(VideoDetector::kMinUpdateWidth, + VideoDetector::kMinUpdateHeight)); + for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) + detector_->OnWindowPaintScheduled(window.get(), update_region); + EXPECT_EQ(1, observer_->num_invocations()); + + // Let enough time pass that a second notification should be sent. + observer_->reset_stats(); + AdvanceTime(base::TimeDelta::FromSeconds( + static_cast<int64>(VideoDetector::kNotifyIntervalSec + 1))); + for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) + detector_->OnWindowPaintScheduled(window.get(), update_region); + EXPECT_EQ(1, observer_->num_invocations()); +} + +} // namespace test +} // namespace ash |