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