diff options
author | derat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-19 22:13:38 +0000 |
---|---|---|
committer | derat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-19 22:13:38 +0000 |
commit | 6aa614a9ac971f5b027eee0749f211883f1d6934 (patch) | |
tree | ab48ab3a1a3e6a9ebebc19f7579f55dd273ebae8 /ash | |
parent | 1d6ed7bc2007d2521d14f8bdaf1962ba499d0b81 (diff) | |
download | chromium_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.gyp | 5 | ||||
-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 | 200 |
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 |