Home | History | Annotate | Download | only in display
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "ash/display/resolution_notification_controller.h"
      6 
      7 #include "ash/display/display_manager.h"
      8 #include "ash/screen_util.h"
      9 #include "ash/shell.h"
     10 #include "ash/test/ash_test_base.h"
     11 #include "base/bind.h"
     12 #include "base/strings/utf_string_conversions.h"
     13 #include "grit/ash_strings.h"
     14 #include "ui/base/l10n/l10n_util.h"
     15 #include "ui/gfx/size.h"
     16 #include "ui/message_center/message_center.h"
     17 #include "ui/message_center/notification.h"
     18 #include "ui/message_center/notification_list.h"
     19 
     20 namespace ash {
     21 namespace {
     22 
     23 base::string16 ExpectedNotificationMessage(int64 display_id,
     24                                            const gfx::Size& new_resolution) {
     25   return l10n_util::GetStringFUTF16(
     26       IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED,
     27       base::UTF8ToUTF16(
     28           Shell::GetInstance()->display_manager()->GetDisplayNameForId(
     29               display_id)),
     30       base::UTF8ToUTF16(new_resolution.ToString()));
     31 }
     32 
     33 base::string16 ExpectedFallbackNotificationMessage(
     34     int64 display_id,
     35     const gfx::Size& specified_resolution,
     36     const gfx::Size& fallback_resolution) {
     37   return l10n_util::GetStringFUTF16(
     38       IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED_TO_UNSUPPORTED,
     39       base::UTF8ToUTF16(
     40           Shell::GetInstance()->display_manager()->GetDisplayNameForId(
     41               display_id)),
     42       base::UTF8ToUTF16(specified_resolution.ToString()),
     43       base::UTF8ToUTF16(fallback_resolution.ToString()));
     44 }
     45 
     46 }  // namespace
     47 
     48 class ResolutionNotificationControllerTest : public ash::test::AshTestBase {
     49  public:
     50   ResolutionNotificationControllerTest()
     51       : accept_count_(0) {
     52   }
     53 
     54   virtual ~ResolutionNotificationControllerTest() {}
     55 
     56  protected:
     57   virtual void SetUp() OVERRIDE {
     58     ash::test::AshTestBase::SetUp();
     59     ResolutionNotificationController::SuppressTimerForTest();
     60   }
     61 
     62   void SetDisplayResolutionAndNotifyWithResolution(
     63       const gfx::Display& display,
     64       const gfx::Size& new_resolution,
     65       const gfx::Size& actual_new_resolution) {
     66     DisplayManager* display_manager = Shell::GetInstance()->display_manager();
     67 
     68     const DisplayInfo& info = display_manager->GetDisplayInfo(display.id());
     69     DisplayMode old_mode(info.size_in_pixel(),
     70                          60 /* refresh_rate */,
     71                          false /* interlaced */,
     72                          false /* native */);
     73     DisplayMode new_mode = old_mode;
     74     new_mode.size = new_resolution;
     75 
     76     if (display_manager->SetDisplayMode(display.id(), new_mode)) {
     77       controller()->PrepareNotification(
     78           display.id(),
     79           old_mode,
     80           new_mode,
     81           base::Bind(&ResolutionNotificationControllerTest::OnAccepted,
     82                      base::Unretained(this)));
     83     }
     84 
     85     // OnConfigurationChanged event won't be emitted in the test environment,
     86     // so invoke UpdateDisplay() to emit that event explicitly.
     87     std::vector<DisplayInfo> info_list;
     88     for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
     89       int64 id = display_manager->GetDisplayAt(i).id();
     90       DisplayInfo info = display_manager->GetDisplayInfo(id);
     91       if (display.id() == id) {
     92         gfx::Rect bounds = info.bounds_in_native();
     93         bounds.set_size(actual_new_resolution);
     94         info.SetBounds(bounds);
     95       }
     96       info_list.push_back(info);
     97     }
     98     display_manager->OnNativeDisplaysChanged(info_list);
     99     RunAllPendingInMessageLoop();
    100   }
    101 
    102   void SetDisplayResolutionAndNotify(const gfx::Display& display,
    103                                      const gfx::Size& new_resolution) {
    104     SetDisplayResolutionAndNotifyWithResolution(
    105         display, new_resolution, new_resolution);
    106   }
    107 
    108   static base::string16 GetNotificationMessage() {
    109     const message_center::NotificationList::Notifications& notifications =
    110         message_center::MessageCenter::Get()->GetVisibleNotifications();
    111     for (message_center::NotificationList::Notifications::const_iterator iter =
    112              notifications.begin(); iter != notifications.end(); ++iter) {
    113       if ((*iter)->id() == ResolutionNotificationController::kNotificationId)
    114         return (*iter)->title();
    115     }
    116 
    117     return base::string16();
    118   }
    119 
    120   static void ClickOnNotification() {
    121     message_center::MessageCenter::Get()->ClickOnNotification(
    122         ResolutionNotificationController::kNotificationId);
    123   }
    124 
    125   static void ClickOnNotificationButton(int index) {
    126     message_center::MessageCenter::Get()->ClickOnNotificationButton(
    127         ResolutionNotificationController::kNotificationId, index);
    128   }
    129 
    130   static void CloseNotification() {
    131     message_center::MessageCenter::Get()->RemoveNotification(
    132         ResolutionNotificationController::kNotificationId, true /* by_user */);
    133   }
    134 
    135   static bool IsNotificationVisible() {
    136     return message_center::MessageCenter::Get()->FindVisibleNotificationById(
    137         ResolutionNotificationController::kNotificationId);
    138   }
    139 
    140   static void TickTimer() {
    141     controller()->OnTimerTick();
    142   }
    143 
    144   static ResolutionNotificationController* controller() {
    145     return Shell::GetInstance()->resolution_notification_controller();
    146   }
    147 
    148   int accept_count() const {
    149     return accept_count_;
    150   }
    151 
    152  private:
    153   void OnAccepted() {
    154     EXPECT_FALSE(controller()->DoesNotificationTimeout());
    155     accept_count_++;
    156   }
    157 
    158   int accept_count_;
    159 
    160   DISALLOW_COPY_AND_ASSIGN(ResolutionNotificationControllerTest);
    161 };
    162 
    163 // Basic behaviors and verifies it doesn't cause crashes.
    164 TEST_F(ResolutionNotificationControllerTest, Basic) {
    165   if (!SupportsMultipleDisplays())
    166     return;
    167 
    168   UpdateDisplay("300x300#300x300%57|200x200%58,250x250#250x250%59|200x200%60");
    169   int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id();
    170   ash::DisplayManager* display_manager =
    171       ash::Shell::GetInstance()->display_manager();
    172   ASSERT_EQ(0, accept_count());
    173   EXPECT_FALSE(IsNotificationVisible());
    174 
    175   // Changes the resolution and apply the result.
    176   SetDisplayResolutionAndNotify(
    177       ScreenUtil::GetSecondaryDisplay(), gfx::Size(200, 200));
    178   EXPECT_TRUE(IsNotificationVisible());
    179   EXPECT_FALSE(controller()->DoesNotificationTimeout());
    180   EXPECT_EQ(ExpectedNotificationMessage(id2, gfx::Size(200, 200)),
    181             GetNotificationMessage());
    182   DisplayMode mode;
    183   EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode));
    184   EXPECT_EQ("200x200", mode.size.ToString());
    185   EXPECT_EQ(60.0, mode.refresh_rate);
    186 
    187   // Click the revert button, which reverts to the best resolution.
    188   ClickOnNotificationButton(0);
    189   RunAllPendingInMessageLoop();
    190   EXPECT_FALSE(IsNotificationVisible());
    191   EXPECT_EQ(0, accept_count());
    192   EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode));
    193   EXPECT_EQ("250x250", mode.size.ToString());
    194   EXPECT_EQ(59.0, mode.refresh_rate);
    195 }
    196 
    197 TEST_F(ResolutionNotificationControllerTest, ClickMeansAccept) {
    198   if (!SupportsMultipleDisplays())
    199     return;
    200 
    201   UpdateDisplay("300x300#300x300%57|200x200%58,250x250#250x250%59|200x200%60");
    202   int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id();
    203   ash::DisplayManager* display_manager =
    204       ash::Shell::GetInstance()->display_manager();
    205   ASSERT_EQ(0, accept_count());
    206   EXPECT_FALSE(IsNotificationVisible());
    207 
    208   // Changes the resolution and apply the result.
    209   SetDisplayResolutionAndNotify(
    210       ScreenUtil::GetSecondaryDisplay(), gfx::Size(200, 200));
    211   EXPECT_TRUE(IsNotificationVisible());
    212   EXPECT_FALSE(controller()->DoesNotificationTimeout());
    213   DisplayMode mode;
    214   EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode));
    215   EXPECT_EQ("200x200", mode.size.ToString());
    216   EXPECT_EQ(60.0, mode.refresh_rate);
    217 
    218   // Click the revert button, which reverts the resolution.
    219   ClickOnNotification();
    220   RunAllPendingInMessageLoop();
    221   EXPECT_FALSE(IsNotificationVisible());
    222   EXPECT_EQ(1, accept_count());
    223   EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode));
    224   EXPECT_EQ("200x200", mode.size.ToString());
    225   EXPECT_EQ(60.0, mode.refresh_rate);
    226 }
    227 
    228 TEST_F(ResolutionNotificationControllerTest, AcceptButton) {
    229   if (!SupportsMultipleDisplays())
    230     return;
    231 
    232   ash::DisplayManager* display_manager =
    233       ash::Shell::GetInstance()->display_manager();
    234 
    235   UpdateDisplay("300x300#300x300%59|200x200%60");
    236   const gfx::Display& display = ash::Shell::GetScreen()->GetPrimaryDisplay();
    237   SetDisplayResolutionAndNotify(display, gfx::Size(200, 200));
    238   EXPECT_TRUE(IsNotificationVisible());
    239 
    240   // If there's a single display only, it will have timeout and the first button
    241   // becomes accept.
    242   EXPECT_TRUE(controller()->DoesNotificationTimeout());
    243   ClickOnNotificationButton(0);
    244   EXPECT_FALSE(IsNotificationVisible());
    245   EXPECT_EQ(1, accept_count());
    246   DisplayMode mode;
    247   EXPECT_TRUE(
    248       display_manager->GetSelectedModeForDisplayId(display.id(), &mode));
    249   EXPECT_EQ("200x200", mode.size.ToString());
    250   EXPECT_EQ(60.0f, mode.refresh_rate);
    251 
    252   // In that case the second button is revert.
    253   UpdateDisplay("300x300#300x300%59|200x200%60");
    254   SetDisplayResolutionAndNotify(display, gfx::Size(200, 200));
    255   EXPECT_TRUE(IsNotificationVisible());
    256 
    257   EXPECT_TRUE(controller()->DoesNotificationTimeout());
    258   ClickOnNotificationButton(1);
    259   EXPECT_FALSE(IsNotificationVisible());
    260   EXPECT_EQ(1, accept_count());
    261   EXPECT_TRUE(
    262       display_manager->GetSelectedModeForDisplayId(display.id(), &mode));
    263   EXPECT_EQ("300x300", mode.size.ToString());
    264   EXPECT_EQ(59.0f, mode.refresh_rate);
    265 }
    266 
    267 TEST_F(ResolutionNotificationControllerTest, Close) {
    268   if (!SupportsMultipleDisplays())
    269     return;
    270 
    271   UpdateDisplay("100x100,150x150#150x150%59|200x200%60");
    272   int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id();
    273   ash::DisplayManager* display_manager =
    274       ash::Shell::GetInstance()->display_manager();
    275   ASSERT_EQ(0, accept_count());
    276   EXPECT_FALSE(IsNotificationVisible());
    277 
    278   // Changes the resolution and apply the result.
    279   SetDisplayResolutionAndNotify(
    280       ScreenUtil::GetSecondaryDisplay(), gfx::Size(200, 200));
    281   EXPECT_TRUE(IsNotificationVisible());
    282   EXPECT_FALSE(controller()->DoesNotificationTimeout());
    283   DisplayMode mode;
    284   EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode));
    285   EXPECT_EQ("200x200", mode.size.ToString());
    286   EXPECT_EQ(60.0f, mode.refresh_rate);
    287 
    288   // Close the notification (imitates clicking [x] button). Also verifies if
    289   // this does not cause a crash.  See crbug.com/271784
    290   CloseNotification();
    291   RunAllPendingInMessageLoop();
    292   EXPECT_FALSE(IsNotificationVisible());
    293   EXPECT_EQ(1, accept_count());
    294 }
    295 
    296 TEST_F(ResolutionNotificationControllerTest, Timeout) {
    297   if (!SupportsMultipleDisplays())
    298     return;
    299 
    300   UpdateDisplay("300x300#300x300%59|200x200%60");
    301   const gfx::Display& display = ash::Shell::GetScreen()->GetPrimaryDisplay();
    302   SetDisplayResolutionAndNotify(display, gfx::Size(200, 200));
    303 
    304   for (int i = 0; i < ResolutionNotificationController::kTimeoutInSec; ++i) {
    305     EXPECT_TRUE(IsNotificationVisible()) << "notification is closed after "
    306                                          << i << "-th timer tick";
    307     TickTimer();
    308     RunAllPendingInMessageLoop();
    309   }
    310   EXPECT_FALSE(IsNotificationVisible());
    311   EXPECT_EQ(0, accept_count());
    312   ash::DisplayManager* display_manager =
    313       ash::Shell::GetInstance()->display_manager();
    314   DisplayMode mode;
    315   EXPECT_TRUE(
    316       display_manager->GetSelectedModeForDisplayId(display.id(), &mode));
    317   EXPECT_EQ("300x300", mode.size.ToString());
    318   EXPECT_EQ(59.0f, mode.refresh_rate);
    319 }
    320 
    321 TEST_F(ResolutionNotificationControllerTest, DisplayDisconnected) {
    322   if (!SupportsMultipleDisplays())
    323     return;
    324 
    325   UpdateDisplay("300x300#300x300%56|200x200%57,"
    326                 "200x200#250x250%58|200x200%59|100x100%60");
    327   int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id();
    328   ash::DisplayManager* display_manager =
    329       ash::Shell::GetInstance()->display_manager();
    330   SetDisplayResolutionAndNotify(
    331       ScreenUtil::GetSecondaryDisplay(), gfx::Size(100, 100));
    332   ASSERT_TRUE(IsNotificationVisible());
    333 
    334   // Disconnects the secondary display and verifies it doesn't cause crashes.
    335   UpdateDisplay("300x300#300x300%56|200x200%57");
    336   RunAllPendingInMessageLoop();
    337   EXPECT_FALSE(IsNotificationVisible());
    338   EXPECT_EQ(0, accept_count());
    339   DisplayMode mode;
    340   EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode));
    341   gfx::Size resolution;
    342   EXPECT_EQ("200x200", mode.size.ToString());
    343   EXPECT_EQ(59.0f, mode.refresh_rate);
    344 }
    345 
    346 TEST_F(ResolutionNotificationControllerTest, MultipleResolutionChange) {
    347   if (!SupportsMultipleDisplays())
    348     return;
    349 
    350   UpdateDisplay("300x300#300x300%56|200x200%57,"
    351                 "250x250#250x250%58|200x200%59");
    352   int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id();
    353   ash::DisplayManager* display_manager =
    354       ash::Shell::GetInstance()->display_manager();
    355 
    356   SetDisplayResolutionAndNotify(
    357       ScreenUtil::GetSecondaryDisplay(), gfx::Size(200, 200));
    358   EXPECT_TRUE(IsNotificationVisible());
    359   EXPECT_FALSE(controller()->DoesNotificationTimeout());
    360   DisplayMode mode;
    361   EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode));
    362   EXPECT_EQ("200x200", mode.size.ToString());
    363   EXPECT_EQ(59.0f, mode.refresh_rate);
    364 
    365   // Invokes SetDisplayResolutionAndNotify during the previous notification is
    366   // visible.
    367   SetDisplayResolutionAndNotify(
    368       ScreenUtil::GetSecondaryDisplay(), gfx::Size(250, 250));
    369   EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode));
    370   EXPECT_EQ("250x250", mode.size.ToString());
    371   EXPECT_EQ(58.0f, mode.refresh_rate);
    372 
    373   // Then, click the revert button. Although |old_resolution| for the second
    374   // SetDisplayResolutionAndNotify is 200x200, it should revert to the original
    375   // size 250x250.
    376   ClickOnNotificationButton(0);
    377   RunAllPendingInMessageLoop();
    378   EXPECT_FALSE(IsNotificationVisible());
    379   EXPECT_EQ(0, accept_count());
    380   EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode));
    381   EXPECT_EQ("250x250", mode.size.ToString());
    382   EXPECT_EQ(58.0f, mode.refresh_rate);
    383 }
    384 
    385 TEST_F(ResolutionNotificationControllerTest, Fallback) {
    386   if (!SupportsMultipleDisplays())
    387     return;
    388 
    389   UpdateDisplay("300x300#300x300%56|200x200%57,"
    390                 "250x250#250x250%58|220x220%59|200x200%60");
    391   int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id();
    392   ash::DisplayManager* display_manager =
    393       ash::Shell::GetInstance()->display_manager();
    394   ASSERT_EQ(0, accept_count());
    395   EXPECT_FALSE(IsNotificationVisible());
    396 
    397   // Changes the resolution and apply the result.
    398   SetDisplayResolutionAndNotifyWithResolution(
    399       ScreenUtil::GetSecondaryDisplay(),
    400       gfx::Size(220, 220),
    401       gfx::Size(200, 200));
    402   EXPECT_TRUE(IsNotificationVisible());
    403   EXPECT_FALSE(controller()->DoesNotificationTimeout());
    404   EXPECT_EQ(
    405       ExpectedFallbackNotificationMessage(
    406           id2, gfx::Size(220, 220), gfx::Size(200, 200)),
    407       GetNotificationMessage());
    408   DisplayMode mode;
    409   EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode));
    410   EXPECT_EQ("200x200", mode.size.ToString());
    411   EXPECT_EQ(60.0f, mode.refresh_rate);
    412 
    413   // Click the revert button, which reverts to the best resolution.
    414   ClickOnNotificationButton(0);
    415   RunAllPendingInMessageLoop();
    416   EXPECT_FALSE(IsNotificationVisible());
    417   EXPECT_EQ(0, accept_count());
    418   EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode));
    419   EXPECT_EQ("250x250", mode.size.ToString());
    420   EXPECT_EQ(58.0f, mode.refresh_rate);
    421 }
    422 
    423 }  // namespace ash
    424