summaryrefslogtreecommitdiffstats
path: root/ash
diff options
context:
space:
mode:
authorderat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-01-19 22:13:38 +0000
committerderat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-01-19 22:13:38 +0000
commit6aa614a9ac971f5b027eee0749f211883f1d6934 (patch)
treeab48ab3a1a3e6a9ebebc19f7579f55dd273ebae8 /ash
parent1d6ed7bc2007d2521d14f8bdaf1962ba499d0b81 (diff)
downloadchromium_src-6aa614a9ac971f5b027eee0749f211883f1d6934.zip
chromium_src-6aa614a9ac971f5b027eee0749f211883f1d6934.tar.gz
chromium_src-6aa614a9ac971f5b027eee0749f211883f1d6934.tar.bz2
Reland "aura/chromeos: Avoid suspending while video is playing."
This reverts r118199, which was a revert of r118171. aura::VideoPropertyWriter (not ash::VideoDetector -- made a typo in the revert's description) was outliving the ash::Shell singleton. Now we explicitly destroy the VideoPropertyWriter before the shell. The only differences from r118171 are the addition of the final chunk in chrome_browser_main_chromeos.cc and a change to reparent a window in VideoDetectorTest.WindowNotVisible needed in response to r118259. BUG=110114 TEST=avoided missing DCHECK on shutdown in trybot logs this time Review URL: http://codereview.chromium.org/9254018 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@118375 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ash')
-rw-r--r--ash/ash.gyp5
-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.cc200
7 files changed, 424 insertions, 2 deletions
diff --git a/ash/ash.gyp b/ash/ash.gyp
index a9a1ec0..333c633 100644
--- a/ash/ash.gyp
+++ b/ash/ash.gyp
@@ -152,8 +152,10 @@
'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_animations.cc',
- 'wm/window_animations.h',
+ 'wm/window_animations.h',
'wm/window_cycle_controller.cc',
'wm/window_cycle_controller.h',
'wm/window_cycle_list.cc',
@@ -230,6 +232,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 3f50105..40df7c9 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -37,6 +37,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"
@@ -266,6 +267,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..b853915
--- /dev/null
+++ b/ash/wm/video_detector_unittest.cc
@@ -0,0 +1,200 @@
+// 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));
+
+ // Reparent the window to the root to make sure that visibility changes aren't
+ // animated.
+ aura::RootWindow::GetInstance()->AddChild(window.get());
+
+ // We shouldn't report video that's played in a hidden window.
+ window->Hide();
+ 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(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