summaryrefslogtreecommitdiffstats
path: root/ash
diff options
context:
space:
mode:
authorderat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-01-18 22:26:40 +0000
committerderat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-01-18 22:26:40 +0000
commita3102766e725636ad23ad99faa1e8a481a1129e7 (patch)
treecea797379b158ff00fb8d4b2e86c67046b548946 /ash
parent8431abd52eb3f7cc0eb9812933fe3eb5e83def45 (diff)
downloadchromium_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.gyp3
-rw-r--r--ash/shell.cc2
-rw-r--r--ash/shell.h5
-rw-r--r--ash/wm/power_button_controller.h3
-rw-r--r--ash/wm/video_detector.cc111
-rw-r--r--ash/wm/video_detector.h100
-rw-r--r--ash/wm/video_detector_unittest.cc196
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