// 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/system/chromeos/tray_display.h" #include "ash/display/display_manager.h" #include "ash/root_window_controller.h" #include "ash/screen_util.h" #include "ash/shell.h" #include "ash/system/tray/system_tray.h" #include "ash/test/ash_test_base.h" #include "ash/test/test_system_tray_delegate.h" #include "base/strings/string16.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "grit/ash_strings.h" #include "ui/accessibility/ax_view_state.h" #include "ui/base/l10n/l10n_util.h" #include "ui/gfx/display.h" #include "ui/message_center/message_center.h" #include "ui/message_center/notification.h" #include "ui/message_center/notification_list.h" #include "ui/views/controls/label.h" namespace ash { base::string16 GetTooltipText(const base::string16& headline, const base::string16& name1, const std::string& data1, const base::string16& name2, const std::string& data2) { std::vector<base::string16> lines; lines.push_back(headline); if (data1.empty()) { lines.push_back(name1); } else { lines.push_back(l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_SINGLE_DISPLAY, name1, base::UTF8ToUTF16(data1))); } if (!name2.empty()) { lines.push_back(l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_SINGLE_DISPLAY, name2, base::UTF8ToUTF16(data2))); } return JoinString(lines, '\n'); } base::string16 GetMirroredTooltipText(const base::string16& headline, const base::string16& name, const std::string& data) { return GetTooltipText(headline, name, data, base::string16(), ""); } base::string16 GetFirstDisplayName() { DisplayManager* display_manager = Shell::GetInstance()->display_manager(); return base::UTF8ToUTF16(display_manager->GetDisplayNameForId( display_manager->first_display_id())); } base::string16 GetSecondDisplayName() { return base::UTF8ToUTF16( Shell::GetInstance()->display_manager()->GetDisplayNameForId( ScreenUtil::GetSecondaryDisplay().id())); } base::string16 GetMirroredDisplayName() { DisplayManager* display_manager = Shell::GetInstance()->display_manager(); return base::UTF8ToUTF16(display_manager->GetDisplayNameForId( display_manager->mirrored_display_id())); } class TrayDisplayTest : public ash::test::AshTestBase { public: TrayDisplayTest(); virtual ~TrayDisplayTest(); virtual void SetUp() OVERRIDE; protected: SystemTray* tray() { return tray_; } TrayDisplay* tray_display() { return tray_display_; } void CloseNotification(); bool IsDisplayVisibleInTray() const; base::string16 GetTrayDisplayText() const; void CheckAccessibleName() const; base::string16 GetTrayDisplayTooltipText() const; base::string16 GetDisplayNotificationText() const; base::string16 GetDisplayNotificationAdditionalText() const; private: const message_center::Notification* GetDisplayNotification() const; // Weak reference, owned by Shell. SystemTray* tray_; // Weak reference, owned by |tray_|. TrayDisplay* tray_display_; DISALLOW_COPY_AND_ASSIGN(TrayDisplayTest); }; TrayDisplayTest::TrayDisplayTest() : tray_(NULL), tray_display_(NULL) { } TrayDisplayTest::~TrayDisplayTest() { } void TrayDisplayTest::SetUp() { ash::test::AshTestBase::SetUp(); tray_ = Shell::GetPrimaryRootWindowController()->GetSystemTray(); tray_display_ = new TrayDisplay(tray_); tray_->AddTrayItem(tray_display_); } void TrayDisplayTest::CloseNotification() { message_center::MessageCenter::Get()->RemoveNotification( TrayDisplay::kNotificationId, false); RunAllPendingInMessageLoop(); } bool TrayDisplayTest::IsDisplayVisibleInTray() const { return tray_->HasSystemBubble() && tray_display_->default_view() && tray_display_->default_view()->visible(); } base::string16 TrayDisplayTest::GetTrayDisplayText() const { return tray_display_->GetDefaultViewMessage(); } void TrayDisplayTest::CheckAccessibleName() const { ui::AXViewState state; if (tray_display_->GetAccessibleStateForTesting(&state)) { base::string16 expected = tray_display_->GetDefaultViewMessage(); EXPECT_EQ(expected, state.name); } } base::string16 TrayDisplayTest::GetTrayDisplayTooltipText() const { if (!tray_display_->default_view()) return base::string16(); base::string16 tooltip; if (!tray_display_->default_view()->GetTooltipText(gfx::Point(), &tooltip)) return base::string16(); return tooltip; } base::string16 TrayDisplayTest::GetDisplayNotificationText() const { const message_center::Notification* notification = GetDisplayNotification(); return notification ? notification->title() : base::string16(); } base::string16 TrayDisplayTest::GetDisplayNotificationAdditionalText() const { const message_center::Notification* notification = GetDisplayNotification(); return notification ? notification->message() : base::string16(); } const message_center::Notification* TrayDisplayTest::GetDisplayNotification() const { const message_center::NotificationList::Notifications notifications = message_center::MessageCenter::Get()->GetVisibleNotifications(); for (message_center::NotificationList::Notifications::const_iterator iter = notifications.begin(); iter != notifications.end(); ++iter) { if ((*iter)->id() == TrayDisplay::kNotificationId) return *iter; } return NULL; } TEST_F(TrayDisplayTest, NoInternalDisplay) { UpdateDisplay("400x400"); tray()->ShowDefaultView(BUBBLE_USE_EXISTING); EXPECT_FALSE(IsDisplayVisibleInTray()); UpdateDisplay("400x400,200x200"); tray()->ShowDefaultView(BUBBLE_USE_EXISTING); EXPECT_TRUE(IsDisplayVisibleInTray()); base::string16 expected = l10n_util::GetStringUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL); base::string16 first_name = GetFirstDisplayName(); EXPECT_EQ(expected, GetTrayDisplayText()); EXPECT_EQ(GetTooltipText(expected, GetFirstDisplayName(), "400x400", GetSecondDisplayName(), "200x200"), GetTrayDisplayTooltipText()); CheckAccessibleName(); // mirroring Shell::GetInstance()->display_manager()->SetSoftwareMirroring(true); UpdateDisplay("400x400,200x200"); tray()->ShowDefaultView(BUBBLE_USE_EXISTING); EXPECT_TRUE(IsDisplayVisibleInTray()); expected = l10n_util::GetStringUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING_NO_INTERNAL); EXPECT_EQ(expected, GetTrayDisplayText()); EXPECT_EQ(GetMirroredTooltipText(expected, GetFirstDisplayName(), "400x400"), GetTrayDisplayTooltipText()); CheckAccessibleName(); } TEST_F(TrayDisplayTest, InternalDisplay) { UpdateDisplay("400x400"); DisplayManager* display_manager = Shell::GetInstance()->display_manager(); gfx::Display::SetInternalDisplayId(display_manager->first_display_id()); tray()->ShowDefaultView(BUBBLE_USE_EXISTING); EXPECT_FALSE(IsDisplayVisibleInTray()); // Extended UpdateDisplay("400x400,200x200"); base::string16 expected = l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetSecondDisplayName()); tray()->ShowDefaultView(BUBBLE_USE_EXISTING); EXPECT_TRUE(IsDisplayVisibleInTray()); EXPECT_EQ(expected, GetTrayDisplayText()); EXPECT_EQ(GetTooltipText(expected, GetFirstDisplayName(), "400x400", GetSecondDisplayName(), "200x200"), GetTrayDisplayTooltipText()); CheckAccessibleName(); // Mirroring display_manager->SetSoftwareMirroring(true); UpdateDisplay("400x400,200x200"); tray()->ShowDefaultView(BUBBLE_USE_EXISTING); EXPECT_TRUE(IsDisplayVisibleInTray()); expected = l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, GetMirroredDisplayName()); EXPECT_EQ(expected, GetTrayDisplayText()); EXPECT_EQ(GetMirroredTooltipText(expected, GetFirstDisplayName(), "400x400"), GetTrayDisplayTooltipText()); CheckAccessibleName(); } TEST_F(TrayDisplayTest, InternalDisplayResized) { UpdateDisplay("400x400@1.5"); DisplayManager* display_manager = Shell::GetInstance()->display_manager(); gfx::Display::SetInternalDisplayId(display_manager->first_display_id()); // Shows the tray_display even though there's a single-display. tray()->ShowDefaultView(BUBBLE_USE_EXISTING); EXPECT_TRUE(IsDisplayVisibleInTray()); base::string16 internal_info = l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_SINGLE_DISPLAY, GetFirstDisplayName(), base::UTF8ToUTF16("600x600")); EXPECT_EQ(internal_info, GetTrayDisplayText()); EXPECT_EQ(GetTooltipText(base::string16(), GetFirstDisplayName(), "600x600", base::string16(), std::string()), GetTrayDisplayTooltipText()); CheckAccessibleName(); // Extended UpdateDisplay("400x400@1.5,200x200"); tray()->ShowDefaultView(BUBBLE_USE_EXISTING); EXPECT_TRUE(IsDisplayVisibleInTray()); base::string16 expected = l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetSecondDisplayName()); EXPECT_EQ(expected, GetTrayDisplayText()); EXPECT_EQ(GetTooltipText(expected, GetFirstDisplayName(), "600x600", GetSecondDisplayName(), "200x200"), GetTrayDisplayTooltipText()); CheckAccessibleName(); // Mirroring display_manager->SetSoftwareMirroring(true); UpdateDisplay("400x400@1.5,200x200"); tray()->ShowDefaultView(BUBBLE_USE_EXISTING); EXPECT_TRUE(IsDisplayVisibleInTray()); expected = l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, GetMirroredDisplayName()); EXPECT_EQ(expected, GetTrayDisplayText()); EXPECT_EQ(GetMirroredTooltipText(expected, GetFirstDisplayName(), "600x600"), GetTrayDisplayTooltipText()); CheckAccessibleName(); // Closed lid mode. display_manager->SetSoftwareMirroring(false); UpdateDisplay("400x400@1.5,200x200"); gfx::Display::SetInternalDisplayId(ScreenUtil::GetSecondaryDisplay().id()); UpdateDisplay("400x400@1.5"); tray()->ShowDefaultView(BUBBLE_USE_EXISTING); EXPECT_TRUE(IsDisplayVisibleInTray()); expected = l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED); EXPECT_EQ(expected, GetTrayDisplayText()); EXPECT_EQ( GetTooltipText( expected, GetFirstDisplayName(), "600x600", base::string16(), ""), GetTrayDisplayTooltipText()); CheckAccessibleName(); } TEST_F(TrayDisplayTest, ExternalDisplayResized) { UpdateDisplay("400x400"); DisplayManager* display_manager = Shell::GetInstance()->display_manager(); gfx::Display::SetInternalDisplayId(display_manager->first_display_id()); // Shows the tray_display even though there's a single-display. tray()->ShowDefaultView(BUBBLE_USE_EXISTING); EXPECT_FALSE(IsDisplayVisibleInTray()); // Extended UpdateDisplay("400x400,200x200@1.5"); const gfx::Display& secondary_display = ScreenUtil::GetSecondaryDisplay(); tray()->ShowDefaultView(BUBBLE_USE_EXISTING); EXPECT_TRUE(IsDisplayVisibleInTray()); base::string16 expected = l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME, GetSecondDisplayName(), base::UTF8ToUTF16(secondary_display.size().ToString()))); EXPECT_EQ(expected, GetTrayDisplayText()); EXPECT_EQ(GetTooltipText(expected, GetFirstDisplayName(), "400x400", GetSecondDisplayName(), "300x300"), GetTrayDisplayTooltipText()); CheckAccessibleName(); // Mirroring display_manager->SetSoftwareMirroring(true); UpdateDisplay("400x400,200x200@1.5"); tray()->ShowDefaultView(BUBBLE_USE_EXISTING); EXPECT_TRUE(IsDisplayVisibleInTray()); expected = l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, GetMirroredDisplayName()); EXPECT_EQ(expected, GetTrayDisplayText()); EXPECT_EQ(GetMirroredTooltipText(expected, GetFirstDisplayName(), "400x400"), GetTrayDisplayTooltipText()); CheckAccessibleName(); } TEST_F(TrayDisplayTest, OverscanDisplay) { UpdateDisplay("400x400,300x300/o"); DisplayManager* display_manager = Shell::GetInstance()->display_manager(); gfx::Display::SetInternalDisplayId(display_manager->first_display_id()); tray()->ShowDefaultView(BUBBLE_USE_EXISTING); EXPECT_TRUE(IsDisplayVisibleInTray()); // /o creates the default overscan, and if overscan is set, the annotation // should be the size. base::string16 overscan = l10n_util::GetStringUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN); base::string16 headline = l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME, GetSecondDisplayName(), base::UTF8ToUTF16("286x286"))); std::string second_data = l10n_util::GetStringFUTF8( IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION, base::UTF8ToUTF16("286x286"), overscan); EXPECT_EQ(GetTooltipText(headline, GetFirstDisplayName(), "400x400", GetSecondDisplayName(), second_data), GetTrayDisplayTooltipText()); // reset the overscan. display_manager->SetOverscanInsets( ScreenUtil::GetSecondaryDisplay().id(), gfx::Insets()); headline = l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME, GetSecondDisplayName(), overscan)); second_data = l10n_util::GetStringFUTF8( IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION, base::UTF8ToUTF16("300x300"), overscan); EXPECT_EQ(GetTooltipText(headline, GetFirstDisplayName(), "400x400", GetSecondDisplayName(), second_data), GetTrayDisplayTooltipText()); } TEST_F(TrayDisplayTest, UpdateDuringDisplayConfigurationChange) { tray()->ShowDefaultView(BUBBLE_USE_EXISTING); EXPECT_FALSE(IsDisplayVisibleInTray()); UpdateDisplay("400x400@1.5"); EXPECT_TRUE(tray()->HasSystemBubble()); EXPECT_TRUE(IsDisplayVisibleInTray()); base::string16 internal_info = l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_SINGLE_DISPLAY, GetFirstDisplayName(), base::UTF8ToUTF16("600x600")); EXPECT_EQ(internal_info, GetTrayDisplayText()); EXPECT_EQ(GetTooltipText(base::string16(), GetFirstDisplayName(), "600x600", base::string16(), std::string()), GetTrayDisplayTooltipText()); CheckAccessibleName(); UpdateDisplay("400x400,200x200"); EXPECT_TRUE(tray()->HasSystemBubble()); EXPECT_TRUE(IsDisplayVisibleInTray()); base::string16 expected = l10n_util::GetStringUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL); base::string16 first_name = GetFirstDisplayName(); EXPECT_EQ(expected, GetTrayDisplayText()); EXPECT_EQ(GetTooltipText(expected, GetFirstDisplayName(), "400x400", GetSecondDisplayName(), "200x200"), GetTrayDisplayTooltipText()); CheckAccessibleName(); UpdateDisplay("400x400@1.5"); tray()->ShowDefaultView(BUBBLE_USE_EXISTING); // Back to the default state, the display tray item should disappear. UpdateDisplay("400x400"); EXPECT_TRUE(tray()->HasSystemBubble()); EXPECT_FALSE(IsDisplayVisibleInTray()); } TEST_F(TrayDisplayTest, DisplayNotifications) { test::TestSystemTrayDelegate* tray_delegate = static_cast<test::TestSystemTrayDelegate*>( Shell::GetInstance()->system_tray_delegate()); tray_delegate->set_should_show_display_notification(true); UpdateDisplay("400x400"); DisplayManager* display_manager = Shell::GetInstance()->display_manager(); gfx::Display::SetInternalDisplayId(display_manager->first_display_id()); EXPECT_TRUE(GetDisplayNotificationText().empty()); // rotation. UpdateDisplay("400x400/r"); EXPECT_EQ( l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetFirstDisplayName(), l10n_util::GetStringUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90)), GetDisplayNotificationText()); EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty()); CloseNotification(); UpdateDisplay("400x400"); EXPECT_EQ( l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetFirstDisplayName(), l10n_util::GetStringUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION)), GetDisplayNotificationText()); EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty()); // UI-scale CloseNotification(); UpdateDisplay("400x400@1.5"); EXPECT_EQ( l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED, GetFirstDisplayName(), base::UTF8ToUTF16("600x600")), GetDisplayNotificationText()); EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty()); // UI-scale to 1.0 CloseNotification(); UpdateDisplay("400x400"); EXPECT_EQ( l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED, GetFirstDisplayName(), base::UTF8ToUTF16("400x400")), GetDisplayNotificationText()); EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty()); // No-update CloseNotification(); UpdateDisplay("400x400"); EXPECT_TRUE(GetDisplayNotificationText().empty()); EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty()); // Extended. CloseNotification(); UpdateDisplay("400x400,200x200"); EXPECT_EQ( l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetSecondDisplayName()), GetDisplayNotificationText()); EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty()); // Mirroring. CloseNotification(); display_manager->SetSoftwareMirroring(true); UpdateDisplay("400x400,200x200"); EXPECT_EQ( l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, GetMirroredDisplayName()), GetDisplayNotificationText()); EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty()); // Back to extended. CloseNotification(); display_manager->SetSoftwareMirroring(false); UpdateDisplay("400x400,200x200"); EXPECT_EQ( l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetSecondDisplayName()), GetDisplayNotificationText()); EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty()); // Resize the first display. UpdateDisplay("400x400@1.5,200x200"); EXPECT_EQ( l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED, GetFirstDisplayName(), base::UTF8ToUTF16("600x600")), GetDisplayNotificationText()); EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty()); // Rotate the second. UpdateDisplay("400x400@1.5,200x200/r"); EXPECT_EQ( l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetSecondDisplayName(), l10n_util::GetStringUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90)), GetDisplayNotificationText()); EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty()); // Enters closed lid mode. UpdateDisplay("400x400@1.5,200x200"); gfx::Display::SetInternalDisplayId(ScreenUtil::GetSecondaryDisplay().id()); UpdateDisplay("400x400@1.5"); EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED), GetDisplayNotificationText()); EXPECT_EQ( l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED_DESCRIPTION), GetDisplayNotificationAdditionalText()); } TEST_F(TrayDisplayTest, DisplayConfigurationChangedTwice) { test::TestSystemTrayDelegate* tray_delegate = static_cast<test::TestSystemTrayDelegate*>( Shell::GetInstance()->system_tray_delegate()); tray_delegate->set_should_show_display_notification(true); UpdateDisplay("400x400,200x200"); EXPECT_EQ( l10n_util::GetStringUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL), GetDisplayNotificationText()); // OnDisplayConfigurationChanged() may be called more than once for a single // update display in case of primary is swapped or recovered from dock mode. // Should not remove the notification in such case. tray_display()->OnDisplayConfigurationChanged(); EXPECT_EQ( l10n_util::GetStringUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL), GetDisplayNotificationText()); // Back to the single display. It SHOULD remove the notification since the // information is stale. UpdateDisplay("400x400"); EXPECT_TRUE(GetDisplayNotificationText().empty()); } TEST_F(TrayDisplayTest, UpdateAfterSuppressDisplayNotification) { UpdateDisplay("400x400,200x200"); test::TestSystemTrayDelegate* tray_delegate = static_cast<test::TestSystemTrayDelegate*>( Shell::GetInstance()->system_tray_delegate()); tray_delegate->set_should_show_display_notification(true); // rotate the second. UpdateDisplay("400x400,200x200/r"); EXPECT_EQ( l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetSecondDisplayName(), l10n_util::GetStringUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90)), GetDisplayNotificationText()); } } // namespace ash