summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormukai@chromium.org <mukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-10 18:13:44 +0000
committermukai@chromium.org <mukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-10 18:13:44 +0000
commit6ef71d70c1c26b6ffd06965ff734b3c121978fa4 (patch)
treeb0a9c86658bd109c7eb2403f30e6fca677ceb68d
parentc9104c687d28387df1492997ca1b24d7e6ad31bd (diff)
downloadchromium_src-6ef71d70c1c26b6ffd06965ff734b3c121978fa4.zip
chromium_src-6ef71d70c1c26b6ffd06965ff734b3c121978fa4.tar.gz
chromium_src-6ef71d70c1c26b6ffd06965ff734b3c121978fa4.tar.bz2
Creates notifications for display resolution change.
The notification notifies the change of the resolution, and contains "Revert" button in case the new resolution doesn't work well. Also it creates timeout in case the change operation is potentially dangerous, which means changing the resolution of the only one display. BUG=230733, 266097 R=oshima@chromium.org, derat@chromium.org TEST=new test cases in ash_unittests Review URL: https://chromiumcodereview.appspot.com/22703004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@216881 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--ash/ash.gyp3
-rw-r--r--ash/ash_strings.grd9
-rw-r--r--ash/display/resolution_notification_controller.cc296
-rw-r--r--ash/display/resolution_notification_controller.h95
-rw-r--r--ash/display/resolution_notification_controller_unittest.cc283
-rw-r--r--ash/shell.cc6
-rw-r--r--ash/shell.h9
-rw-r--r--chrome/browser/chromeos/display/display_preferences.cc7
-rw-r--r--chrome/browser/chromeos/display/display_preferences_unittest.cc49
-rw-r--r--chrome/browser/ui/webui/options/chromeos/display_options_handler.cc32
10 files changed, 784 insertions, 5 deletions
diff --git a/ash/ash.gyp b/ash/ash.gyp
index 82fb264..33d729e 100644
--- a/ash/ash.gyp
+++ b/ash/ash.gyp
@@ -108,6 +108,8 @@
'display/mouse_cursor_event_filter.h',
'display/output_configurator_animation.cc',
'display/output_configurator_animation.h',
+ 'display/resolution_notification_controller.cc',
+ 'display/resolution_notification_controller.h',
'display/root_window_transformers.cc',
'display/root_window_transformers.h',
'display/screen_position_controller.cc',
@@ -673,6 +675,7 @@
'display/display_manager_unittest.cc',
'display/mirror_window_controller_unittest.cc',
'display/mouse_cursor_event_filter_unittest.cc',
+ 'display/resolution_notification_controller_unittest.cc',
'display/root_window_transformers_unittest.cc',
'display/screen_position_controller_unittest.cc',
'drag_drop/drag_drop_controller_unittest.cc',
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 4c6d2b9..7bb2b9f 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -536,6 +536,15 @@ Press Shift + Alt to switch.
<message name="IDS_ASH_DISPLAY_FAILURE_ON_NON_MIRRORING" desc="An error message to show that the system failed to enter the extended desktop mode or unknown status. Please translate the parentized text.">
Dear Monitor, it's not working out between us. (That monitor is not supported)
</message>
+ <message name="IDS_ASH_DISPLAY_RESOLUTION_CHANGE_ACCEPT" desc="A button label shown in the notification for the resolution change to accept the change">
+ Accept
+ </message>
+ <message name="IDS_ASH_DISPLAY_RESOLUTION_CHANGE_REVERT" desc="A button label shown in the notification for the resolution change to revert the change">
+ Revert
+ </message>
+ <message name="IDS_ASH_DISPLAY_RESOLUTION_TIMEOUT" desc="A message for to notify the user about the timeout of display resolution change.">
+ Reverting to old resolution in <ph name="TIMEOUT_SECONDS">$1</ph>
+ </message>
<message name="IDS_ASH_INTERNAL_DISPLAY_NAME" desc="The name of the internal display which is shown in the display settings.">
Internal Display
</message>
diff --git a/ash/display/resolution_notification_controller.cc b/ash/display/resolution_notification_controller.cc
new file mode 100644
index 0000000..a765d61
--- /dev/null
+++ b/ash/display/resolution_notification_controller.cc
@@ -0,0 +1,296 @@
+// Copyright 2013 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/display/resolution_notification_controller.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/display/display_manager.h"
+#include "ash/shell.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/time_format.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/notification.h"
+#include "ui/message_center/notification_delegate.h"
+
+using message_center::Notification;
+
+namespace ash {
+namespace internal {
+namespace {
+
+bool g_use_timer = true;
+
+class ResolutionChangeNotificationDelegate
+ : public message_center::NotificationDelegate {
+ public:
+ ResolutionChangeNotificationDelegate(
+ ResolutionNotificationController* controller,
+ bool has_timeout);
+
+ protected:
+ virtual ~ResolutionChangeNotificationDelegate();
+
+ private:
+ // message_center::NotificationDelegate overrides:
+ virtual void Display() OVERRIDE;
+ virtual void Error() OVERRIDE;
+ virtual void Close(bool by_user) OVERRIDE;
+ virtual void Click() OVERRIDE;
+ virtual bool HasClickedListener() OVERRIDE;
+ virtual void ButtonClick(int button_index) OVERRIDE;
+
+ ResolutionNotificationController* controller_;
+ bool has_timeout_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResolutionChangeNotificationDelegate);
+};
+
+ResolutionChangeNotificationDelegate::ResolutionChangeNotificationDelegate(
+ ResolutionNotificationController* controller,
+ bool has_timeout)
+ : controller_(controller),
+ has_timeout_(has_timeout) {
+ DCHECK(controller_);
+}
+
+ResolutionChangeNotificationDelegate::~ResolutionChangeNotificationDelegate() {
+}
+
+void ResolutionChangeNotificationDelegate::Display() {
+}
+
+void ResolutionChangeNotificationDelegate::Error() {
+}
+
+void ResolutionChangeNotificationDelegate::Close(bool by_user) {
+ if (by_user)
+ controller_->AcceptResolutionChange();
+}
+
+void ResolutionChangeNotificationDelegate::Click() {
+ controller_->AcceptResolutionChange();
+}
+
+bool ResolutionChangeNotificationDelegate::HasClickedListener() {
+ return true;
+}
+
+void ResolutionChangeNotificationDelegate::ButtonClick(int button_index) {
+ // If there's the timeout, the first button is "Accept". Otherwise the
+ // button click should be "Revert".
+ if (has_timeout_ && button_index == 0)
+ controller_->AcceptResolutionChange();
+ else
+ controller_->RevertResolutionChange();
+}
+
+} // namespace
+
+// static
+const int ResolutionNotificationController::kTimeoutInSec = 15;
+
+// static
+const char ResolutionNotificationController::kNotificationId[] =
+ "chrome://settings/display/resolution";
+
+struct ResolutionNotificationController::ResolutionChangeInfo {
+ ResolutionChangeInfo(int64 display_id,
+ const gfx::Size& old_resolution,
+ const gfx::Size& new_resolution,
+ const base::Closure& accept_callback);
+ ~ResolutionChangeInfo();
+
+ // The id of the display where the resolution change happens.
+ int64 display_id;
+
+ // The resolution before the change.
+ gfx::Size old_resolution;
+
+ // The new resolution after the change.
+ gfx::Size new_resolution;
+
+ // The callback when accept is chosen.
+ base::Closure accept_callback;
+
+ // The remaining timeout in seconds. 0 if the change does not time out.
+ uint8 timeout_count;
+
+ // The timer to invoke OnTimerTick() every second. This cannot be
+ // OneShotTimer since the message contains text "automatically closed in xx
+ // seconds..." which has to be updated every second.
+ base::RepeatingTimer<ResolutionNotificationController> timer;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResolutionChangeInfo);
+};
+
+ResolutionNotificationController::ResolutionChangeInfo::ResolutionChangeInfo(
+ int64 display_id,
+ const gfx::Size& old_resolution,
+ const gfx::Size& new_resolution,
+ const base::Closure& accept_callback)
+ : display_id(display_id),
+ old_resolution(old_resolution),
+ new_resolution(new_resolution),
+ accept_callback(accept_callback),
+ timeout_count(0) {
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ if (!display_manager->HasInternalDisplay() &&
+ display_manager->num_connected_displays() == 1u) {
+ timeout_count = kTimeoutInSec;
+ }
+}
+
+ResolutionNotificationController::ResolutionChangeInfo::
+ ~ResolutionChangeInfo() {
+}
+
+ResolutionNotificationController::ResolutionNotificationController() {
+ Shell::GetInstance()->display_controller()->AddObserver(this);
+ Shell::GetScreen()->AddObserver(this);
+}
+
+ResolutionNotificationController::~ResolutionNotificationController() {
+ Shell::GetInstance()->display_controller()->RemoveObserver(this);
+ Shell::GetScreen()->RemoveObserver(this);
+}
+
+void ResolutionNotificationController::SetDisplayResolutionAndNotify(
+ int64 display_id,
+ const gfx::Size& old_resolution,
+ const gfx::Size& new_resolution,
+ const base::Closure& accept_callback) {
+ // If multiple resolution changes are invoked for the same display,
+ // the original resolution for the first resolution change has to be used
+ // instead of the specified |old_resolution|.
+ gfx::Size original_resolution;
+ if (change_info_ && change_info_->display_id == display_id) {
+ DCHECK(change_info_->new_resolution == old_resolution);
+ original_resolution = change_info_->old_resolution;
+ }
+
+ change_info_.reset(new ResolutionChangeInfo(
+ display_id, old_resolution, new_resolution, accept_callback));
+ if (!original_resolution.IsEmpty())
+ change_info_->old_resolution = original_resolution;
+
+ // SetDisplayResolution() causes OnConfigurationChanged() and the notification
+ // will be shown at that point.
+ Shell::GetInstance()->display_manager()->SetDisplayResolution(
+ display_id, new_resolution);
+}
+
+bool ResolutionNotificationController::DoesNotificationTimeout() {
+ return change_info_ && change_info_->timeout_count > 0;
+}
+
+void ResolutionNotificationController::CreateOrUpdateNotification() {
+ message_center::MessageCenter* message_center =
+ message_center::MessageCenter::Get();
+ if (!change_info_) {
+ message_center->RemoveNotification(kNotificationId, false /* by_user */);
+ return;
+ }
+
+ base::string16 timeout_message;
+ message_center::RichNotificationData data;
+ if (change_info_->timeout_count > 0) {
+ data.buttons.push_back(message_center::ButtonInfo(
+ l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_RESOLUTION_CHANGE_ACCEPT)));
+ timeout_message = l10n_util::GetStringFUTF16(
+ IDS_ASH_DISPLAY_RESOLUTION_TIMEOUT,
+ ui::TimeFormat::TimeDurationLong(
+ base::TimeDelta::FromSeconds(change_info_->timeout_count)));
+ }
+ data.buttons.push_back(message_center::ButtonInfo(
+ l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_RESOLUTION_CHANGE_REVERT)));
+
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ scoped_ptr<Notification> notification(new Notification(
+ message_center::NOTIFICATION_TYPE_SIMPLE,
+ kNotificationId,
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED,
+ UTF8ToUTF16(Shell::GetInstance()->display_manager()->
+ GetDisplayNameForId(change_info_->display_id)),
+ UTF8ToUTF16(change_info_->new_resolution.ToString())),
+ timeout_message,
+ bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY),
+ base::string16() /* display_source */,
+ std::string() /* extension_id */,
+ data,
+ new ResolutionChangeNotificationDelegate(
+ this, change_info_->timeout_count > 0)));
+ notification->SetSystemPriority();
+ message_center->AddNotification(notification.Pass());
+}
+
+void ResolutionNotificationController::OnTimerTick() {
+ if (!change_info_)
+ return;
+
+ --change_info_->timeout_count;
+ if (change_info_->timeout_count == 0)
+ RevertResolutionChange();
+ else
+ CreateOrUpdateNotification();
+}
+
+void ResolutionNotificationController::AcceptResolutionChange() {
+ message_center::MessageCenter::Get()->RemoveNotification(
+ kNotificationId, false /* by_user */);
+ base::Closure callback = change_info_->accept_callback;
+ change_info_.reset();
+ callback.Run();
+}
+
+void ResolutionNotificationController::RevertResolutionChange() {
+ message_center::MessageCenter::Get()->RemoveNotification(
+ kNotificationId, false /* by_user */);
+ int64 display_id = change_info_->display_id;
+ gfx::Size old_resolution = change_info_->old_resolution;
+ change_info_.reset();
+ Shell::GetInstance()->display_manager()->SetDisplayResolution(
+ display_id, old_resolution);
+}
+
+void ResolutionNotificationController::OnDisplayBoundsChanged(
+ const gfx::Display& display) {
+}
+
+void ResolutionNotificationController::OnDisplayAdded(
+ const gfx::Display& new_display) {
+}
+
+void ResolutionNotificationController::OnDisplayRemoved(
+ const gfx::Display& old_display) {
+ if (change_info_ && change_info_->display_id == old_display.id())
+ RevertResolutionChange();
+}
+
+void ResolutionNotificationController::OnDisplayConfigurationChanged() {
+ if (!change_info_)
+ return;
+
+ CreateOrUpdateNotification();
+ if (g_use_timer && change_info_->timeout_count > 0) {
+ change_info_->timer.Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(1),
+ this,
+ &ResolutionNotificationController::OnTimerTick);
+ }
+}
+
+void ResolutionNotificationController::SuppressTimerForTest() {
+ g_use_timer = false;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/ash/display/resolution_notification_controller.h b/ash/display/resolution_notification_controller.h
new file mode 100644
index 0000000..4de39c2
--- /dev/null
+++ b/ash/display/resolution_notification_controller.h
@@ -0,0 +1,95 @@
+// Copyright 2013 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_DISPLAY_RESOLUTION_NOTIFICATION_CONTROLLER_H_
+#define ASH_DISPLAY_RESOLUTION_NOTIFICATION_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "ash/display/display_controller.h"
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/timer/timer.h"
+#include "ui/gfx/display_observer.h"
+#include "ui/gfx/size.h"
+
+namespace chromeos {
+FORWARD_DECLARE_TEST(DisplayPreferencesTest, PreventStore);
+} // namespace chromeos
+
+namespace views {
+class Label;
+class Widget;
+} // namespace views
+
+namespace ash {
+namespace internal {
+// A class which manages the notification of display resolution change and
+// also manages the timeout in case the new resolution is unusable.
+class ASH_EXPORT ResolutionNotificationController
+ : public gfx::DisplayObserver,
+ public DisplayController::Observer {
+ public:
+ ResolutionNotificationController();
+ virtual ~ResolutionNotificationController();
+
+ // Updates the display resolution for |display_id| to |new_resolution| and
+ // creates a notification for this change which offers a button to revert the
+ // change in case something goes wrong. The notification times out if there's
+ // only one display connected and the user is trying to modify its resolution.
+ // In that case, the timeout has to be set since the user cannot make any
+ // changes if something goes wrong.
+ void SetDisplayResolutionAndNotify(
+ int64 display_id,
+ const gfx::Size& old_resolution,
+ const gfx::Size& new_resolution,
+ const base::Closure& accept_callback);
+
+ // Returns true if the notification is visible or scheduled to be visible and
+ // the notification times out.
+ bool DoesNotificationTimeout();
+
+ // Called by the notification delegate when the user accepts the display
+ // resolution change.
+ void AcceptResolutionChange();
+
+ // Called by the notification delegate when the user wants to revert the
+ // display resolution change.
+ void RevertResolutionChange();
+
+ private:
+ friend class ResolutionNotificationControllerTest;
+ FRIEND_TEST_ALL_PREFIXES(ResolutionNotificationControllerTest, Timeout);
+ FRIEND_TEST_ALL_PREFIXES(chromeos::DisplayPreferencesTest, PreventStore);
+
+ // A struct to bundle the data for a single resolution change.
+ struct ResolutionChangeInfo;
+
+ static const int kTimeoutInSec;
+ static const char kNotificationId[];
+
+ // Create a new notification, or update its content if it already exists.
+ void CreateOrUpdateNotification();
+
+ // Called every second for timeout.
+ void OnTimerTick();
+
+ // gfx::DisplayObserver overrides:
+ virtual void OnDisplayBoundsChanged(const gfx::Display& display) OVERRIDE;
+ virtual void OnDisplayAdded(const gfx::Display& new_display) OVERRIDE;
+ virtual void OnDisplayRemoved(const gfx::Display& old_display) OVERRIDE;
+
+ // DisplayController::Observer overrides:
+ virtual void OnDisplayConfigurationChanged() OVERRIDE;
+
+ static void SuppressTimerForTest();
+
+ scoped_ptr<ResolutionChangeInfo> change_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResolutionNotificationController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DISPLAY_RESOLUTION_NOTIFICATION_CONTROLLER_H_
diff --git a/ash/display/resolution_notification_controller_unittest.cc b/ash/display/resolution_notification_controller_unittest.cc
new file mode 100644
index 0000000..bdb6f12
--- /dev/null
+++ b/ash/display/resolution_notification_controller_unittest.cc
@@ -0,0 +1,283 @@
+// Copyright 2013 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/display/resolution_notification_controller.h"
+
+#include "ash/display/display_manager.h"
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "base/bind.h"
+#include "ui/gfx/size.h"
+#include "ui/message_center/message_center.h"
+
+namespace ash {
+namespace internal {
+
+class ResolutionNotificationControllerTest : public ash::test::AshTestBase {
+ public:
+ ResolutionNotificationControllerTest()
+ : accept_count_(0) {
+ }
+
+ virtual ~ResolutionNotificationControllerTest() {}
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ ash::test::AshTestBase::SetUp();
+ ResolutionNotificationController::SuppressTimerForTest();
+ }
+
+ void SetDisplayResolutionAndNotify(const gfx::Display& display,
+ const gfx::Size& new_resolution) {
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ const DisplayInfo& info = display_manager->GetDisplayInfo(display.id());
+ Shell::GetInstance()->resolution_notification_controller()->
+ SetDisplayResolutionAndNotify(
+ display.id(),
+ info.size_in_pixel(),
+ new_resolution,
+ base::Bind(&ResolutionNotificationControllerTest::OnAccepted,
+ base::Unretained(this)));
+
+ // OnConfigurationChanged event won't be emitted in the test environment,
+ // so invoke UpdateDisplay() to emit that event explicitly.
+ std::string display_spec;
+ for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
+ if (i > 0)
+ display_spec.append(",");
+ int64 id = display_manager->GetDisplayAt(i).id();
+ gfx::Size size = (display.id() == id) ?
+ new_resolution : display_manager->GetDisplayInfo(id).size_in_pixel();
+ display_spec.append(size.ToString());
+ }
+ UpdateDisplay(display_spec);
+ RunAllPendingInMessageLoop();
+ }
+
+ void ClickOnNotification() {
+ message_center::MessageCenter::Get()->ClickOnNotification(
+ ResolutionNotificationController::kNotificationId);
+ }
+
+ void ClickOnNotificationButton(int index) {
+ message_center::MessageCenter::Get()->ClickOnNotificationButton(
+ ResolutionNotificationController::kNotificationId, index);
+ }
+
+ bool IsNotificationVisible() {
+ return message_center::MessageCenter::Get()->HasNotification(
+ ResolutionNotificationController::kNotificationId);
+ }
+
+ void TickTimer() {
+ controller()->OnTimerTick();
+ }
+
+ ResolutionNotificationController* controller() {
+ return Shell::GetInstance()->resolution_notification_controller();
+ }
+
+ int accept_count() const {
+ return accept_count_;
+ }
+
+ private:
+ void OnAccepted() {
+ EXPECT_FALSE(controller()->DoesNotificationTimeout());
+ accept_count_++;
+ }
+
+ int accept_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResolutionNotificationControllerTest);
+};
+
+// Basic behaviors and verifies it doesn't cause crashes.
+TEST_F(ResolutionNotificationControllerTest, Basic) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("100x100,150x150");
+ int64 id2 = ash::ScreenAsh::GetSecondaryDisplay().id();
+ ash::internal::DisplayManager* display_manager =
+ ash::Shell::GetInstance()->display_manager();
+ ASSERT_EQ(0, accept_count());
+ EXPECT_FALSE(IsNotificationVisible());
+
+ // Changes the resolution and apply the result.
+ SetDisplayResolutionAndNotify(
+ ScreenAsh::GetSecondaryDisplay(), gfx::Size(200, 200));
+ EXPECT_TRUE(IsNotificationVisible());
+ EXPECT_FALSE(controller()->DoesNotificationTimeout());
+ gfx::Size resolution;
+ EXPECT_TRUE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+ EXPECT_EQ("200x200", resolution.ToString());
+
+ // Click the revert button, which reverts the resolution.
+ ClickOnNotificationButton(0);
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(IsNotificationVisible());
+ EXPECT_EQ(0, accept_count());
+ EXPECT_TRUE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+ EXPECT_EQ("150x150", resolution.ToString());
+}
+
+TEST_F(ResolutionNotificationControllerTest, ClickMeansAccept) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("100x100,150x150");
+ int64 id2 = ash::ScreenAsh::GetSecondaryDisplay().id();
+ ash::internal::DisplayManager* display_manager =
+ ash::Shell::GetInstance()->display_manager();
+ ASSERT_EQ(0, accept_count());
+ EXPECT_FALSE(IsNotificationVisible());
+
+ // Changes the resolution and apply the result.
+ SetDisplayResolutionAndNotify(
+ ScreenAsh::GetSecondaryDisplay(), gfx::Size(200, 200));
+ EXPECT_TRUE(IsNotificationVisible());
+ EXPECT_FALSE(controller()->DoesNotificationTimeout());
+ gfx::Size resolution;
+ EXPECT_TRUE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+ EXPECT_EQ("200x200", resolution.ToString());
+
+ // Click the revert button, which reverts the resolution.
+ ClickOnNotification();
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(IsNotificationVisible());
+ EXPECT_EQ(1, accept_count());
+ EXPECT_TRUE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+ EXPECT_EQ("200x200", resolution.ToString());
+}
+
+TEST_F(ResolutionNotificationControllerTest, AcceptButton) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ ash::internal::DisplayManager* display_manager =
+ ash::Shell::GetInstance()->display_manager();
+
+ UpdateDisplay("100x100");
+ const gfx::Display& display = ash::Shell::GetScreen()->GetPrimaryDisplay();
+ SetDisplayResolutionAndNotify(display, gfx::Size(200, 200));
+ EXPECT_TRUE(IsNotificationVisible());
+
+ // If there's a single display only, it will have timeout and the first button
+ // becomes accept.
+ EXPECT_TRUE(controller()->DoesNotificationTimeout());
+ ClickOnNotificationButton(0);
+ EXPECT_FALSE(IsNotificationVisible());
+ EXPECT_EQ(1, accept_count());
+ gfx::Size resolution;
+ EXPECT_TRUE(display_manager->GetSelectedResolutionForDisplayId(
+ display.id(), &resolution));
+ EXPECT_EQ("200x200", resolution.ToString());
+
+ // In that case the second button is revert.
+ UpdateDisplay("100x100");
+ SetDisplayResolutionAndNotify(display, gfx::Size(200, 200));
+ EXPECT_TRUE(IsNotificationVisible());
+
+ EXPECT_TRUE(controller()->DoesNotificationTimeout());
+ ClickOnNotificationButton(1);
+ EXPECT_FALSE(IsNotificationVisible());
+ EXPECT_EQ(1, accept_count());
+ EXPECT_TRUE(display_manager->GetSelectedResolutionForDisplayId(
+ display.id(), &resolution));
+ EXPECT_EQ("100x100", resolution.ToString());
+}
+
+TEST_F(ResolutionNotificationControllerTest, Timeout) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("100x100");
+ const gfx::Display& display = ash::Shell::GetScreen()->GetPrimaryDisplay();
+ SetDisplayResolutionAndNotify(display, gfx::Size(200, 200));
+
+ for (int i = 0; i < ResolutionNotificationController::kTimeoutInSec; ++i) {
+ EXPECT_TRUE(IsNotificationVisible()) << "notification is closed after "
+ << i << "-th timer tick";
+ TickTimer();
+ RunAllPendingInMessageLoop();
+ }
+ EXPECT_FALSE(IsNotificationVisible());
+ EXPECT_EQ(0, accept_count());
+ gfx::Size resolution;
+ ash::internal::DisplayManager* display_manager =
+ ash::Shell::GetInstance()->display_manager();
+ EXPECT_TRUE(display_manager->GetSelectedResolutionForDisplayId(
+ display.id(), &resolution));
+ EXPECT_EQ("100x100", resolution.ToString());
+}
+
+TEST_F(ResolutionNotificationControllerTest, DisplayDisconnected) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("100x100,150x150");
+ int64 id2 = ash::ScreenAsh::GetSecondaryDisplay().id();
+ ash::internal::DisplayManager* display_manager =
+ ash::Shell::GetInstance()->display_manager();
+ SetDisplayResolutionAndNotify(
+ ScreenAsh::GetSecondaryDisplay(), gfx::Size(200, 200));
+ ASSERT_TRUE(IsNotificationVisible());
+
+ // Disconnects the secondary display and verifies it doesn't cause crashes.
+ UpdateDisplay("100x100");
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(IsNotificationVisible());
+ EXPECT_EQ(0, accept_count());
+ gfx::Size resolution;
+ EXPECT_TRUE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+ EXPECT_EQ("150x150", resolution.ToString());
+}
+
+TEST_F(ResolutionNotificationControllerTest, MultipleResolutionChange) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("100x100,150x150");
+ int64 id2 = ash::ScreenAsh::GetSecondaryDisplay().id();
+ ash::internal::DisplayManager* display_manager =
+ ash::Shell::GetInstance()->display_manager();
+
+ SetDisplayResolutionAndNotify(
+ ScreenAsh::GetSecondaryDisplay(), gfx::Size(200, 200));
+ EXPECT_TRUE(IsNotificationVisible());
+ EXPECT_FALSE(controller()->DoesNotificationTimeout());
+ gfx::Size resolution;
+ EXPECT_TRUE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+ EXPECT_EQ("200x200", resolution.ToString());
+
+ // Invokes SetDisplayResolutionAndNotify during the previous notification is
+ // visible.
+ SetDisplayResolutionAndNotify(
+ ScreenAsh::GetSecondaryDisplay(), gfx::Size(250, 250));
+ EXPECT_TRUE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+ EXPECT_EQ("250x250", resolution.ToString());
+
+ // Then, click the revert button. Although |old_resolution| for the second
+ // SetDisplayResolutionAndNotify is 200x200, it should revert to the original
+ // size 150x150.
+ ClickOnNotificationButton(0);
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(IsNotificationVisible());
+ EXPECT_EQ(0, accept_count());
+ EXPECT_TRUE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+ EXPECT_EQ("150x150", resolution.ToString());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/ash/shell.cc b/ash/shell.cc
index d1a057f..996c817 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -17,6 +17,7 @@
#include "ash/display/display_manager.h"
#include "ash/display/event_transformation_handler.h"
#include "ash/display/mouse_cursor_event_filter.h"
+#include "ash/display/resolution_notification_controller.h"
#include "ash/display/screen_position_controller.h"
#include "ash/drag_drop/drag_drop_controller.h"
#include "ash/focus_cycler.h"
@@ -312,6 +313,8 @@ Shell::~Shell() {
lock_state_controller_.reset();
mru_window_tracker_.reset();
+ resolution_notification_controller_.reset();
+
// This also deletes all RootWindows. Note that we invoke Shutdown() on
// DisplayController before resetting |display_controller_|, since destruction
// of its owned RootWindowControllers relies on the value.
@@ -505,6 +508,9 @@ void Shell::Init() {
aura::RootWindow* root_window = display_controller_->GetPrimaryRootWindow();
target_root_window_ = root_window;
+ resolution_notification_controller_.reset(
+ new internal::ResolutionNotificationController);
+
cursor_manager_.SetDisplay(DisplayController::GetPrimaryDisplay());
#if !defined(OS_MACOSX)
diff --git a/ash/shell.h b/ash/shell.h
index c3adade..ae08e7b 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -115,6 +115,7 @@ class MouseCursorEventFilter;
class OutputConfiguratorAnimation;
class OverlayEventFilter;
class ResizeShadowController;
+class ResolutionNotificationController;
class RootWindowController;
class RootWindowLayoutManager;
class ScopedTargetRootWindow;
@@ -450,6 +451,11 @@ class ASH_EXPORT Shell
}
#endif // defined(OS_CHROMEOS) && defined(USE_X11)
+ internal::ResolutionNotificationController*
+ resolution_notification_controller() {
+ return resolution_notification_controller_.get();
+ }
+
RootWindowHostFactory* root_window_host_factory() {
return root_window_host_factory_.get();
}
@@ -607,6 +613,9 @@ class ASH_EXPORT Shell
scoped_ptr<internal::DisplayChangeObserverX11> display_change_observer_;
#endif // defined(OS_CHROMEOS) && defined(USE_X11)
+ scoped_ptr<internal::ResolutionNotificationController>
+ resolution_notification_controller_;
+
// |native_cursor_manager_| is owned by |cursor_manager_|, but we keep a
// pointer to vend to test code.
AshNativeCursorManager* native_cursor_manager_;
diff --git a/chrome/browser/chromeos/display/display_preferences.cc b/chrome/browser/chromeos/display/display_preferences.cc
index 168404c..14c18ff 100644
--- a/chrome/browser/chromeos/display/display_preferences.cc
+++ b/chrome/browser/chromeos/display/display_preferences.cc
@@ -8,6 +8,7 @@
#include "ash/display/display_layout_store.h"
#include "ash/display/display_manager.h"
#include "ash/display/display_pref_util.h"
+#include "ash/display/resolution_notification_controller.h"
#include "ash/shell.h"
#include "base/prefs/pref_registry_simple.h"
#include "base/prefs/pref_service.h"
@@ -258,8 +259,12 @@ void RegisterDisplayLocalStatePrefs(PrefRegistrySimple* registry) {
}
void StoreDisplayPrefs() {
- if (!IsValidUser())
+ // Do not store prefs when the confirmation dialog is shown.
+ if (!IsValidUser() ||
+ ash::Shell::GetInstance()->resolution_notification_controller()->
+ DoesNotificationTimeout()) {
return;
+ }
StoreCurrentDisplayLayoutPrefs();
StoreCurrentDisplayProperties();
StoreCurrentDisplayPowerState();
diff --git a/chrome/browser/chromeos/display/display_preferences_unittest.cc b/chrome/browser/chromeos/display/display_preferences_unittest.cc
index a319c5b..8299957 100644
--- a/chrome/browser/chromeos/display/display_preferences_unittest.cc
+++ b/chrome/browser/chromeos/display/display_preferences_unittest.cc
@@ -7,6 +7,7 @@
#include "ash/display/display_controller.h"
#include "ash/display/display_layout_store.h"
#include "ash/display/display_manager.h"
+#include "ash/display/resolution_notification_controller.h"
#include "ash/screen_ash.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
@@ -20,6 +21,9 @@
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chromeos/display/output_configurator.h"
+#include "ui/message_center/message_center.h"
+
+using ash::internal::ResolutionNotificationController;
namespace chromeos {
namespace {
@@ -40,6 +44,7 @@ class DisplayPreferencesTest : public ash::test::AshTestBase {
virtual void SetUp() OVERRIDE {
EXPECT_CALL(*mock_user_manager_, IsUserLoggedIn())
.WillRepeatedly(testing::Return(false));
+ EXPECT_CALL(*mock_user_manager_, Shutdown());
ash::test::AshTestBase::SetUp();
RegisterDisplayLocalStatePrefs(local_state_.registry());
TestingBrowserProcess::GetGlobal()->SetLocalState(&local_state_);
@@ -146,6 +151,8 @@ class DisplayPreferencesTest : public ash::test::AshTestBase {
DISALLOW_COPY_AND_ASSIGN(DisplayPreferencesTest);
};
+} // namespace
+
TEST_F(DisplayPreferencesTest, PairedLayoutOverrides) {
UpdateDisplay("100x100,200x200");
int64 id1 = gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().id();
@@ -352,6 +359,47 @@ TEST_F(DisplayPreferencesTest, BasicStores) {
EXPECT_EQ(400, height);
}
+TEST_F(DisplayPreferencesTest, PreventStore) {
+ ResolutionNotificationController::SuppressTimerForTest();
+ LoggedInAsUser();
+ UpdateDisplay("400x300");
+ int64 id = ash::Shell::GetScreen()->GetPrimaryDisplay().id();
+ // Set display's resolution in single display. It creates the notification and
+ // display preferences should not stored meanwhile.
+ ash::Shell::GetInstance()->resolution_notification_controller()->
+ SetDisplayResolutionAndNotify(
+ id, gfx::Size(400, 300), gfx::Size(500, 400), base::Closure());
+ UpdateDisplay("500x400");
+
+ const base::DictionaryValue* properties =
+ local_state()->GetDictionary(prefs::kDisplayProperties);
+ const base::DictionaryValue* property = NULL;
+ EXPECT_TRUE(properties->GetDictionary(base::Int64ToString(id), &property));
+ int width = 0, height = 0;
+ EXPECT_FALSE(property->GetInteger("width", &width));
+ EXPECT_FALSE(property->GetInteger("height", &height));
+
+ // Revert the change. When timeout, 2nd button is revert.
+ message_center::MessageCenter::Get()->ClickOnNotificationButton(
+ ResolutionNotificationController::kNotificationId, 1);
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(message_center::MessageCenter::Get()->HasNotification(
+ ResolutionNotificationController::kNotificationId));
+
+ // Once the notification is removed, the specified resolution will be stored
+ // by SetDisplayResolution.
+ ash::Shell::GetInstance()->display_manager()->SetDisplayResolution(
+ id, gfx::Size(300, 200));
+ UpdateDisplay("300x200");
+
+ property = NULL;
+ EXPECT_TRUE(properties->GetDictionary(base::Int64ToString(id), &property));
+ EXPECT_TRUE(property->GetInteger("width", &width));
+ EXPECT_TRUE(property->GetInteger("height", &height));
+ EXPECT_EQ(300, width);
+ EXPECT_EQ(200, height);
+}
+
TEST_F(DisplayPreferencesTest, StoreForSwappedDisplay) {
UpdateDisplay("100x100,200x200");
int64 id1 = gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().id();
@@ -445,5 +493,4 @@ TEST_F(DisplayPreferencesTest, DisplayPowerStateAfterRestart) {
ash::Shell::GetInstance()->output_configurator()->power_state());
}
-} // namespace
} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options/chromeos/display_options_handler.cc b/chrome/browser/ui/webui/options/chromeos/display_options_handler.cc
index 8714639..d02c3b3 100644
--- a/chrome/browser/ui/webui/options/chromeos/display_options_handler.cc
+++ b/chrome/browser/ui/webui/options/chromeos/display_options_handler.cc
@@ -9,6 +9,7 @@
#include "ash/display/display_controller.h"
#include "ash/display/display_manager.h"
#include "ash/display/output_configurator_animation.h"
+#include "ash/display/resolution_notification_controller.h"
#include "ash/screen_ash.h"
#include "ash/shell.h"
#include "base/bind.h"
@@ -344,9 +345,34 @@ void DisplayOptionsHandler::HandleSetResolution(const base::ListValue* args) {
return;
}
- // TODO(mukai): creates a confirmation dialog.
- GetDisplayManager()->SetDisplayResolution(
- display_id, gfx::ToFlooredSize(gfx::SizeF(width, height)));
+ const ash::internal::DisplayInfo& display_info =
+ GetDisplayManager()->GetDisplayInfo(display_id);
+ gfx::Size new_resolution = gfx::ToFlooredSize(gfx::SizeF(width, height));
+ gfx::Size old_resolution = display_info.size_in_pixel();
+ bool has_new_resolution = false;
+ bool has_old_resolution = false;
+ for (size_t i = 0; i < display_info.resolutions().size(); ++i) {
+ ash::internal::Resolution resolution = display_info.resolutions()[i];
+ if (resolution.size == new_resolution)
+ has_new_resolution = true;
+ if (resolution.size == old_resolution)
+ has_old_resolution = true;
+ }
+ if (!has_new_resolution) {
+ LOG(ERROR) << "No new resolution " << new_resolution.ToString()
+ << " is found in the display info " << display_info.ToString();
+ return;
+ }
+ if (!has_old_resolution) {
+ LOG(ERROR) << "No old resolution " << old_resolution.ToString()
+ << " is found in the display info " << display_info.ToString();
+ return;
+ }
+
+ ash::Shell::GetInstance()->resolution_notification_controller()->
+ SetDisplayResolutionAndNotify(
+ display_id, old_resolution, new_resolution,
+ base::Bind(&StoreDisplayPrefs));
}
void DisplayOptionsHandler::HandleSetOrientation(const base::ListValue* args) {