1 // Copyright (c) 2012 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.
5 #include "ash/system/chromeos/tray_display.h"
9 #include "ash/content/display/screen_orientation_controller_chromeos.h"
10 #include "ash/display/display_manager.h"
11 #include "ash/display/window_tree_host_manager.h"
12 #include "ash/shell.h"
13 #include "ash/system/chromeos/devicetype_utils.h"
14 #include "ash/system/system_notifier.h"
15 #include "ash/system/tray/actionable_view.h"
16 #include "ash/system/tray/fixed_sized_image_view.h"
17 #include "ash/system/tray/system_tray.h"
18 #include "ash/system/tray/system_tray_delegate.h"
19 #include "ash/system/tray/tray_constants.h"
20 #include "ash/system/tray/tray_notification_view.h"
21 #include "base/bind.h"
22 #include "base/strings/string_util.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "grit/ash_resources.h"
25 #include "grit/ash_strings.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/base/resource/resource_bundle.h"
28 #include "ui/message_center/message_center.h"
29 #include "ui/message_center/notification.h"
30 #include "ui/message_center/notification_delegate.h"
31 #include "ui/views/controls/image_view.h"
32 #include "ui/views/controls/label.h"
33 #include "ui/views/layout/box_layout.h"
35 using message_center::Notification
;
40 DisplayManager
* GetDisplayManager() {
41 return Shell::GetInstance()->display_manager();
44 base::string16
GetDisplayName(int64 display_id
) {
45 return base::UTF8ToUTF16(
46 GetDisplayManager()->GetDisplayNameForId(display_id
));
49 base::string16
GetDisplaySize(int64 display_id
) {
50 DisplayManager
* display_manager
= GetDisplayManager();
52 const gfx::Display
* display
= &display_manager
->GetDisplayForId(display_id
);
54 // We don't show display size for mirrored display. Fallback
55 // to empty string if this happens on release build.
56 bool mirroring
= display_manager
->mirroring_display_id() == display_id
;
59 return base::string16();
61 DCHECK(display
->is_valid());
62 return base::UTF8ToUTF16(display
->size().ToString());
65 // Returns 1-line information for the specified display, like
66 // "InternalDisplay: 1280x750"
67 base::string16
GetDisplayInfoLine(int64 display_id
) {
68 const DisplayInfo
& display_info
=
69 GetDisplayManager()->GetDisplayInfo(display_id
);
70 if (GetDisplayManager()->mirroring_display_id() == display_id
)
71 return GetDisplayName(display_id
);
73 base::string16 size_text
= GetDisplaySize(display_id
);
74 base::string16 display_data
;
75 if (display_info
.has_overscan()) {
76 display_data
= l10n_util::GetStringFUTF16(
77 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION
,
79 l10n_util::GetStringUTF16(
80 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN
));
82 display_data
= size_text
;
85 return l10n_util::GetStringFUTF16(
86 IDS_ASH_STATUS_TRAY_DISPLAY_SINGLE_DISPLAY
,
87 GetDisplayName(display_id
),
91 base::string16
GetAllDisplayInfo() {
92 DisplayManager
* display_manager
= GetDisplayManager();
93 std::vector
<base::string16
> lines
;
94 int64 internal_id
= gfx::Display::kInvalidDisplayID
;
95 // Make sure to show the internal display first.
96 if (!display_manager
->IsInUnifiedMode() &&
97 gfx::Display::IsInternalDisplayId(display_manager
->first_display_id())) {
98 internal_id
= display_manager
->first_display_id();
99 lines
.push_back(GetDisplayInfoLine(internal_id
));
102 for (size_t i
= 0; i
< display_manager
->GetNumDisplays(); ++i
) {
103 int64 id
= display_manager
->GetDisplayAt(i
).id();
104 if (id
== internal_id
)
106 lines
.push_back(GetDisplayInfoLine(id
));
109 return base::JoinString(lines
, base::ASCIIToUTF16("\n"));
112 void OpenSettings() {
113 // switch is intentionally introduced without default, to cause an error when
114 // a new type of login status is introduced.
115 switch (Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus()) {
116 case user::LOGGED_IN_NONE
:
117 case user::LOGGED_IN_LOCKED
:
120 case user::LOGGED_IN_USER
:
121 case user::LOGGED_IN_OWNER
:
122 case user::LOGGED_IN_GUEST
:
123 case user::LOGGED_IN_PUBLIC
:
124 case user::LOGGED_IN_SUPERVISED
:
125 case user::LOGGED_IN_KIOSK_APP
:
126 ash::SystemTrayDelegate
* delegate
=
127 Shell::GetInstance()->system_tray_delegate();
128 if (delegate
->ShouldShowSettings())
129 delegate
->ShowDisplaySettings();
135 const char TrayDisplay::kNotificationId
[] = "chrome://settings/display";
137 class DisplayView
: public ActionableView
{
139 explicit DisplayView() {
140 SetLayoutManager(new views::BoxLayout(
141 views::BoxLayout::kHorizontal
,
142 kTrayPopupPaddingHorizontal
, 0,
143 kTrayPopupPaddingBetweenItems
));
145 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
146 image_
= new FixedSizedImageView(0, kTrayPopupItemHeight
);
148 bundle
.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY
).ToImageSkia());
149 AddChildView(image_
);
151 label_
= new views::Label();
152 label_
->SetMultiLine(true);
153 label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
154 AddChildView(label_
);
158 ~DisplayView() override
{}
161 base::string16 message
= GetTrayDisplayMessage(NULL
);
162 if (message
.empty() && ShouldShowFirstDisplayInfo())
163 message
= GetDisplayInfoLine(GetDisplayManager()->first_display_id());
164 SetVisible(!message
.empty());
165 label_
->SetText(message
);
166 SetAccessibleName(message
);
170 const views::Label
* label() const { return label_
; }
172 // Overridden from views::View.
173 bool GetTooltipText(const gfx::Point
& p
,
174 base::string16
* tooltip
) const override
{
175 base::string16 tray_message
= GetTrayDisplayMessage(NULL
);
176 base::string16 display_message
= GetAllDisplayInfo();
177 if (tray_message
.empty() && display_message
.empty())
180 *tooltip
= tray_message
+ base::ASCIIToUTF16("\n") + display_message
;
184 // Returns the name of the currently connected external display.
185 // This should not be used when the external display is used for
187 static base::string16
GetExternalDisplayName() {
188 DisplayManager
* display_manager
= GetDisplayManager();
189 DCHECK(!display_manager
->IsInMirrorMode());
191 int64 external_id
= gfx::Display::kInvalidDisplayID
;
192 for (size_t i
= 0; i
< display_manager
->GetNumDisplays(); ++i
) {
193 int64 id
= display_manager
->GetDisplayAt(i
).id();
194 if (!gfx::Display::IsInternalDisplayId(id
)) {
200 if (external_id
== gfx::Display::kInvalidDisplayID
) {
201 return l10n_util::GetStringUTF16(
202 IDS_ASH_STATUS_TRAY_UNKNOWN_DISPLAY_NAME
);
205 // The external display name may have an annotation of "(width x height)" in
206 // case that the display is rotated or its resolution is changed.
207 base::string16 name
= GetDisplayName(external_id
);
208 const DisplayInfo
& display_info
=
209 display_manager
->GetDisplayInfo(external_id
);
210 if (display_info
.GetActiveRotation() != gfx::Display::ROTATE_0
||
211 display_info
.configured_ui_scale() != 1.0f
||
212 !display_info
.overscan_insets_in_dip().empty()) {
213 name
= l10n_util::GetStringFUTF16(
214 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME
,
215 name
, GetDisplaySize(external_id
));
216 } else if (display_info
.overscan_insets_in_dip().empty() &&
217 display_info
.has_overscan()) {
218 name
= l10n_util::GetStringFUTF16(
219 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME
,
220 name
, l10n_util::GetStringUTF16(
221 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN
));
227 static base::string16
GetTrayDisplayMessage(
228 base::string16
* additional_message_out
) {
229 DisplayManager
* display_manager
= GetDisplayManager();
230 if (display_manager
->GetNumDisplays() > 1) {
231 if (gfx::Display::HasInternalDisplay()) {
232 return l10n_util::GetStringFUTF16(
233 IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED
, GetExternalDisplayName());
235 return l10n_util::GetStringUTF16(
236 IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL
);
239 if (display_manager
->IsInMirrorMode()) {
240 if (gfx::Display::HasInternalDisplay()) {
241 return l10n_util::GetStringFUTF16(
242 IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING
,
243 GetDisplayName(display_manager
->mirroring_display_id()));
245 return l10n_util::GetStringUTF16(
246 IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING_NO_INTERNAL
);
249 if (display_manager
->IsInUnifiedMode())
250 return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_UNIFIED
);
252 int64 primary_id
= Shell::GetScreen()->GetPrimaryDisplay().id();
253 if (gfx::Display::HasInternalDisplay() &&
254 !(gfx::Display::IsInternalDisplayId(primary_id
))) {
255 if (additional_message_out
) {
256 *additional_message_out
= ash::SubstituteChromeOSDeviceType(
257 IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED_DESCRIPTION
);
259 return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED
);
262 return base::string16();
266 bool ShouldShowFirstDisplayInfo() const {
267 const DisplayInfo
& display_info
= GetDisplayManager()->GetDisplayInfo(
268 GetDisplayManager()->first_display_id());
269 return display_info
.GetActiveRotation() != gfx::Display::ROTATE_0
||
270 display_info
.configured_ui_scale() != 1.0f
||
271 !display_info
.overscan_insets_in_dip().empty() ||
272 display_info
.has_overscan();
275 // Overridden from ActionableView.
276 bool PerformAction(const ui::Event
& event
) override
{
281 void OnBoundsChanged(const gfx::Rect
& previous_bounds
) override
{
282 int label_max_width
= bounds().width() - kTrayPopupPaddingHorizontal
* 2 -
283 kTrayPopupPaddingBetweenItems
- image_
->GetPreferredSize().width();
284 label_
->SizeToFit(label_max_width
);
287 views::ImageView
* image_
;
288 views::Label
* label_
;
290 DISALLOW_COPY_AND_ASSIGN(DisplayView
);
293 TrayDisplay::TrayDisplay(SystemTray
* system_tray
)
294 : SystemTrayItem(system_tray
),
296 Shell::GetInstance()->window_tree_host_manager()->AddObserver(this);
297 UpdateDisplayInfo(NULL
);
300 TrayDisplay::~TrayDisplay() {
301 Shell::GetInstance()->window_tree_host_manager()->RemoveObserver(this);
304 void TrayDisplay::UpdateDisplayInfo(TrayDisplay::DisplayInfoMap
* old_info
) {
306 old_info
->swap(display_info_
);
307 display_info_
.clear();
309 DisplayManager
* display_manager
= GetDisplayManager();
310 for (size_t i
= 0; i
< display_manager
->GetNumDisplays(); ++i
) {
311 int64 id
= display_manager
->GetDisplayAt(i
).id();
312 display_info_
[id
] = display_manager
->GetDisplayInfo(id
);
316 bool TrayDisplay::GetDisplayMessageForNotification(
317 const TrayDisplay::DisplayInfoMap
& old_info
,
318 base::string16
* message_out
,
319 base::string16
* additional_message_out
) {
320 // Display is added or removed. Use the same message as the one in
322 if (display_info_
.size() != old_info
.size()) {
323 *message_out
= DisplayView::GetTrayDisplayMessage(additional_message_out
);
327 for (DisplayInfoMap::const_iterator iter
= display_info_
.begin();
328 iter
!= display_info_
.end(); ++iter
) {
329 DisplayInfoMap::const_iterator old_iter
= old_info
.find(iter
->first
);
330 // The display's number is same but different displays. This happens
331 // for the transition between docked mode and mirrored display. Falls back
332 // to GetTrayDisplayMessage().
333 if (old_iter
== old_info
.end()) {
334 *message_out
= DisplayView::GetTrayDisplayMessage(additional_message_out
);
338 if (iter
->second
.configured_ui_scale() !=
339 old_iter
->second
.configured_ui_scale()) {
340 *additional_message_out
= l10n_util::GetStringFUTF16(
341 IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED
,
342 GetDisplayName(iter
->first
), GetDisplaySize(iter
->first
));
345 if (iter
->second
.GetActiveRotation() !=
346 old_iter
->second
.GetActiveRotation()) {
347 int rotation_text_id
= 0;
348 switch (iter
->second
.GetActiveRotation()) {
349 case gfx::Display::ROTATE_0
:
350 rotation_text_id
= IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION
;
352 case gfx::Display::ROTATE_90
:
353 rotation_text_id
= IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90
;
355 case gfx::Display::ROTATE_180
:
356 rotation_text_id
= IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180
;
358 case gfx::Display::ROTATE_270
:
359 rotation_text_id
= IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270
;
362 *additional_message_out
= l10n_util::GetStringFUTF16(
363 IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED
, GetDisplayName(iter
->first
),
364 l10n_util::GetStringUTF16(rotation_text_id
));
369 // Found nothing special
373 void TrayDisplay::CreateOrUpdateNotification(
374 const base::string16
& message
,
375 const base::string16
& additional_message
) {
376 // Always remove the notification to make sure the notification appears
377 // as a popup in any situation.
378 message_center::MessageCenter::Get()->RemoveNotification(
379 kNotificationId
, false /* by_user */);
381 if (message
.empty() && additional_message
.empty())
384 // Don't display notifications for accelerometer triggered screen rotations.
385 // See http://crbug.com/364949
386 if (Shell::GetInstance()
387 ->screen_orientation_controller()
388 ->ignore_display_configuration_updates()) {
392 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
393 scoped_ptr
<Notification
> notification(new Notification(
394 message_center::NOTIFICATION_TYPE_SIMPLE
, kNotificationId
, message
,
395 additional_message
, bundle
.GetImageNamed(IDR_AURA_NOTIFICATION_DISPLAY
),
396 base::string16(), // display_source
398 message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT
,
399 system_notifier::kNotifierDisplay
),
400 message_center::RichNotificationData(),
401 new message_center::HandleNotificationClickedDelegate(
402 base::Bind(&OpenSettings
))));
404 message_center::MessageCenter::Get()->AddNotification(notification
.Pass());
407 views::View
* TrayDisplay::CreateDefaultView(user::LoginStatus status
) {
408 DCHECK(default_
== NULL
);
409 default_
= new DisplayView();
413 void TrayDisplay::DestroyDefaultView() {
417 void TrayDisplay::OnDisplayConfigurationChanged() {
418 DisplayInfoMap old_info
;
419 UpdateDisplayInfo(&old_info
);
424 if (!Shell::GetInstance()->system_tray_delegate()->
425 ShouldShowDisplayNotification()) {
429 base::string16 message
;
430 base::string16 additional_message
;
431 if (GetDisplayMessageForNotification(old_info
, &message
, &additional_message
))
432 CreateOrUpdateNotification(message
, additional_message
);
435 base::string16
TrayDisplay::GetDefaultViewMessage() const {
436 if (!default_
|| !default_
->visible())
437 return base::string16();
439 return static_cast<DisplayView
*>(default_
)->label()->text();
442 bool TrayDisplay::GetAccessibleStateForTesting(ui::AXViewState
* state
) {
443 views::View
* view
= default_
;
445 view
->GetAccessibleState(state
);