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.
4
5 #include "ash/system/chromeos/tray_display.h"
6
7 #include "ash/display/display_controller.h"
8 #include "ash/display/display_manager.h"
9 #include "ash/shell.h"
10 #include "ash/system/system_notifier.h"
11 #include "ash/system/tray/actionable_view.h"
12 #include "ash/system/tray/fixed_sized_image_view.h"
13 #include "ash/system/tray/system_tray.h"
14 #include "ash/system/tray/system_tray_delegate.h"
15 #include "ash/system/tray/tray_constants.h"
16 #include "ash/system/tray/tray_notification_view.h"
17 #include "ash/wm/maximize_mode/maximize_mode_controller.h"
18 #include "base/bind.h"
19 #include "base/strings/string_util.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "grit/ash_resources.h"
22 #include "grit/ash_strings.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/resource/resource_bundle.h"
25 #include "ui/message_center/message_center.h"
26 #include "ui/message_center/notification.h"
27 #include "ui/message_center/notification_delegate.h"
28 #include "ui/views/controls/image_view.h"
29 #include "ui/views/controls/label.h"
30 #include "ui/views/layout/box_layout.h"
31
32 using message_center::Notification;
33
34 namespace ash {
35 namespace {
36
GetDisplayManager()37 DisplayManager* GetDisplayManager() {
38 return Shell::GetInstance()->display_manager();
39 }
40
GetDisplayName(int64 display_id)41 base::string16 GetDisplayName(int64 display_id) {
42 return base::UTF8ToUTF16(
43 GetDisplayManager()->GetDisplayNameForId(display_id));
44 }
45
GetDisplaySize(int64 display_id)46 base::string16 GetDisplaySize(int64 display_id) {
47 DisplayManager* display_manager = GetDisplayManager();
48
49 const gfx::Display* display = &display_manager->GetDisplayForId(display_id);
50
51 // We don't show display size for mirrored display. Fallback
52 // to empty string if this happens on release build.
53 bool mirrored_display = display_manager->mirrored_display_id() == display_id;
54 DCHECK(!mirrored_display);
55 if (mirrored_display)
56 return base::string16();
57
58 DCHECK(display->is_valid());
59 return base::UTF8ToUTF16(display->size().ToString());
60 }
61
62 // Returns 1-line information for the specified display, like
63 // "InternalDisplay: 1280x750"
GetDisplayInfoLine(int64 display_id)64 base::string16 GetDisplayInfoLine(int64 display_id) {
65 const DisplayInfo& display_info =
66 GetDisplayManager()->GetDisplayInfo(display_id);
67 if (GetDisplayManager()->mirrored_display_id() == display_id)
68 return GetDisplayName(display_id);
69
70 base::string16 size_text = GetDisplaySize(display_id);
71 base::string16 display_data;
72 if (display_info.has_overscan()) {
73 display_data = l10n_util::GetStringFUTF16(
74 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION,
75 size_text,
76 l10n_util::GetStringUTF16(
77 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN));
78 } else {
79 display_data = size_text;
80 }
81
82 return l10n_util::GetStringFUTF16(
83 IDS_ASH_STATUS_TRAY_DISPLAY_SINGLE_DISPLAY,
84 GetDisplayName(display_id),
85 display_data);
86 }
87
GetAllDisplayInfo()88 base::string16 GetAllDisplayInfo() {
89 DisplayManager* display_manager = GetDisplayManager();
90 std::vector<base::string16> lines;
91 int64 internal_id = gfx::Display::kInvalidDisplayID;
92 // Make sure to show the internal display first.
93 if (display_manager->HasInternalDisplay() &&
94 display_manager->IsInternalDisplayId(
95 display_manager->first_display_id())) {
96 internal_id = display_manager->first_display_id();
97 lines.push_back(GetDisplayInfoLine(internal_id));
98 }
99
100 for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
101 int64 id = display_manager->GetDisplayAt(i).id();
102 if (id == internal_id)
103 continue;
104 lines.push_back(GetDisplayInfoLine(id));
105 }
106
107 return JoinString(lines, '\n');
108 }
109
OpenSettings()110 void OpenSettings() {
111 // switch is intentionally introduced without default, to cause an error when
112 // a new type of login status is introduced.
113 switch (Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus()) {
114 case user::LOGGED_IN_NONE:
115 case user::LOGGED_IN_LOCKED:
116 return;
117
118 case user::LOGGED_IN_USER:
119 case user::LOGGED_IN_OWNER:
120 case user::LOGGED_IN_GUEST:
121 case user::LOGGED_IN_RETAIL_MODE:
122 case user::LOGGED_IN_PUBLIC:
123 case user::LOGGED_IN_LOCALLY_MANAGED:
124 case user::LOGGED_IN_KIOSK_APP:
125 Shell::GetInstance()->system_tray_delegate()->ShowDisplaySettings();
126 }
127 }
128
129 } // namespace
130
131 const char TrayDisplay::kNotificationId[] = "chrome://settings/display";
132
133 class DisplayView : public ActionableView {
134 public:
DisplayView()135 explicit DisplayView() {
136 SetLayoutManager(new views::BoxLayout(
137 views::BoxLayout::kHorizontal,
138 kTrayPopupPaddingHorizontal, 0,
139 kTrayPopupPaddingBetweenItems));
140
141 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
142 image_ = new FixedSizedImageView(0, kTrayPopupItemHeight);
143 image_->SetImage(
144 bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY).ToImageSkia());
145 AddChildView(image_);
146
147 label_ = new views::Label();
148 label_->SetMultiLine(true);
149 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
150 AddChildView(label_);
151 Update();
152 }
153
~DisplayView()154 virtual ~DisplayView() {}
155
Update()156 void Update() {
157 base::string16 message = GetTrayDisplayMessage(NULL);
158 if (message.empty() && ShouldShowFirstDisplayInfo())
159 message = GetDisplayInfoLine(GetDisplayManager()->first_display_id());
160 SetVisible(!message.empty());
161 label_->SetText(message);
162 SetAccessibleName(message);
163 Layout();
164 }
165
label() const166 const views::Label* label() const { return label_; }
167
168 // Overridden from views::View.
GetTooltipText(const gfx::Point & p,base::string16 * tooltip) const169 virtual bool GetTooltipText(const gfx::Point& p,
170 base::string16* tooltip) const OVERRIDE {
171 base::string16 tray_message = GetTrayDisplayMessage(NULL);
172 base::string16 display_message = GetAllDisplayInfo();
173 if (tray_message.empty() && display_message.empty())
174 return false;
175
176 *tooltip = tray_message + base::ASCIIToUTF16("\n") + display_message;
177 return true;
178 }
179
180 // Returns the name of the currently connected external display.
181 // This should not be used when the external display is used for
182 // mirroring.
GetExternalDisplayName()183 static base::string16 GetExternalDisplayName() {
184 DisplayManager* display_manager = GetDisplayManager();
185 DCHECK(!display_manager->IsMirrored());
186
187 int64 external_id = gfx::Display::kInvalidDisplayID;
188 for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
189 int64 id = display_manager->GetDisplayAt(i).id();
190 if (id != gfx::Display::InternalDisplayId()) {
191 external_id = id;
192 break;
193 }
194 }
195
196 if (external_id == gfx::Display::kInvalidDisplayID) {
197 return l10n_util::GetStringUTF16(
198 IDS_ASH_STATUS_TRAY_UNKNOWN_DISPLAY_NAME);
199 }
200
201 // The external display name may have an annotation of "(width x height)" in
202 // case that the display is rotated or its resolution is changed.
203 base::string16 name = GetDisplayName(external_id);
204 const DisplayInfo& display_info =
205 display_manager->GetDisplayInfo(external_id);
206 if (display_info.rotation() != gfx::Display::ROTATE_0 ||
207 display_info.configured_ui_scale() != 1.0f ||
208 !display_info.overscan_insets_in_dip().empty()) {
209 name = l10n_util::GetStringFUTF16(
210 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
211 name, GetDisplaySize(external_id));
212 } else if (display_info.overscan_insets_in_dip().empty() &&
213 display_info.has_overscan()) {
214 name = l10n_util::GetStringFUTF16(
215 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
216 name, l10n_util::GetStringUTF16(
217 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN));
218 }
219
220 return name;
221 }
222
GetTrayDisplayMessage(base::string16 * additional_message_out)223 static base::string16 GetTrayDisplayMessage(
224 base::string16* additional_message_out) {
225 DisplayManager* display_manager = GetDisplayManager();
226 if (display_manager->GetNumDisplays() > 1) {
227 if (GetDisplayManager()->HasInternalDisplay()) {
228 return l10n_util::GetStringFUTF16(
229 IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetExternalDisplayName());
230 }
231 return l10n_util::GetStringUTF16(
232 IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL);
233 }
234
235 if (display_manager->IsMirrored()) {
236 if (GetDisplayManager()->HasInternalDisplay()) {
237 return l10n_util::GetStringFUTF16(
238 IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING,
239 GetDisplayName(display_manager->mirrored_display_id()));
240 }
241 return l10n_util::GetStringUTF16(
242 IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING_NO_INTERNAL);
243 }
244
245 int64 primary_id = Shell::GetScreen()->GetPrimaryDisplay().id();
246 if (display_manager->HasInternalDisplay() &&
247 !display_manager->IsInternalDisplayId(primary_id)) {
248 if (additional_message_out) {
249 *additional_message_out = l10n_util::GetStringUTF16(
250 IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED_DESCRIPTION);
251 }
252 return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED);
253 }
254
255 return base::string16();
256 }
257
258 private:
ShouldShowFirstDisplayInfo() const259 bool ShouldShowFirstDisplayInfo() const {
260 const DisplayInfo& display_info = GetDisplayManager()->GetDisplayInfo(
261 GetDisplayManager()->first_display_id());
262 return display_info.rotation() != gfx::Display::ROTATE_0 ||
263 display_info.configured_ui_scale() != 1.0f ||
264 !display_info.overscan_insets_in_dip().empty() ||
265 display_info.has_overscan();
266 }
267
268 // Overridden from ActionableView.
PerformAction(const ui::Event & event)269 virtual bool PerformAction(const ui::Event& event) OVERRIDE {
270 OpenSettings();
271 return true;
272 }
273
OnBoundsChanged(const gfx::Rect & previous_bounds)274 virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE {
275 int label_max_width = bounds().width() - kTrayPopupPaddingHorizontal * 2 -
276 kTrayPopupPaddingBetweenItems - image_->GetPreferredSize().width();
277 label_->SizeToFit(label_max_width);
278 }
279
280 views::ImageView* image_;
281 views::Label* label_;
282
283 DISALLOW_COPY_AND_ASSIGN(DisplayView);
284 };
285
TrayDisplay(SystemTray * system_tray)286 TrayDisplay::TrayDisplay(SystemTray* system_tray)
287 : SystemTrayItem(system_tray),
288 default_(NULL) {
289 Shell::GetInstance()->display_controller()->AddObserver(this);
290 UpdateDisplayInfo(NULL);
291 }
292
~TrayDisplay()293 TrayDisplay::~TrayDisplay() {
294 Shell::GetInstance()->display_controller()->RemoveObserver(this);
295 }
296
UpdateDisplayInfo(TrayDisplay::DisplayInfoMap * old_info)297 void TrayDisplay::UpdateDisplayInfo(TrayDisplay::DisplayInfoMap* old_info) {
298 if (old_info)
299 old_info->swap(display_info_);
300 display_info_.clear();
301
302 DisplayManager* display_manager = GetDisplayManager();
303 for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
304 int64 id = display_manager->GetDisplayAt(i).id();
305 display_info_[id] = display_manager->GetDisplayInfo(id);
306 }
307 }
308
GetDisplayMessageForNotification(const TrayDisplay::DisplayInfoMap & old_info,base::string16 * message_out,base::string16 * additional_message_out)309 bool TrayDisplay::GetDisplayMessageForNotification(
310 const TrayDisplay::DisplayInfoMap& old_info,
311 base::string16* message_out,
312 base::string16* additional_message_out) {
313 // Display is added or removed. Use the same message as the one in
314 // the system tray.
315 if (display_info_.size() != old_info.size()) {
316 *message_out = DisplayView::GetTrayDisplayMessage(additional_message_out);
317 return true;
318 }
319
320 for (DisplayInfoMap::const_iterator iter = display_info_.begin();
321 iter != display_info_.end(); ++iter) {
322 DisplayInfoMap::const_iterator old_iter = old_info.find(iter->first);
323 // The display's number is same but different displays. This happens
324 // for the transition between docked mode and mirrored display. Falls back
325 // to GetTrayDisplayMessage().
326 if (old_iter == old_info.end()) {
327 *message_out = DisplayView::GetTrayDisplayMessage(additional_message_out);
328 return true;
329 }
330
331 if (iter->second.configured_ui_scale() !=
332 old_iter->second.configured_ui_scale()) {
333 *message_out = l10n_util::GetStringFUTF16(
334 IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED,
335 GetDisplayName(iter->first),
336 GetDisplaySize(iter->first));
337 return true;
338 }
339 if (iter->second.rotation() != old_iter->second.rotation()) {
340 int rotation_text_id = 0;
341 switch (iter->second.rotation()) {
342 case gfx::Display::ROTATE_0:
343 rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION;
344 break;
345 case gfx::Display::ROTATE_90:
346 rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90;
347 break;
348 case gfx::Display::ROTATE_180:
349 rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180;
350 break;
351 case gfx::Display::ROTATE_270:
352 rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270;
353 break;
354 }
355 *message_out = l10n_util::GetStringFUTF16(
356 IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED,
357 GetDisplayName(iter->first),
358 l10n_util::GetStringUTF16(rotation_text_id));
359 return true;
360 }
361 }
362
363 // Found nothing special
364 return false;
365 }
366
CreateOrUpdateNotification(const base::string16 & message,const base::string16 & additional_message)367 void TrayDisplay::CreateOrUpdateNotification(
368 const base::string16& message,
369 const base::string16& additional_message) {
370 // Always remove the notification to make sure the notification appears
371 // as a popup in any situation.
372 message_center::MessageCenter::Get()->RemoveNotification(
373 kNotificationId, false /* by_user */);
374
375 if (message.empty())
376 return;
377
378 // Don't display notifications for accelerometer triggered screen rotations.
379 // See http://crbug.com/364949
380 if (Shell::GetInstance()->maximize_mode_controller()->
381 in_set_screen_rotation()) {
382 return;
383 }
384
385 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
386 scoped_ptr<Notification> notification(new Notification(
387 message_center::NOTIFICATION_TYPE_SIMPLE,
388 kNotificationId,
389 message,
390 additional_message,
391 bundle.GetImageNamed(IDR_AURA_NOTIFICATION_DISPLAY),
392 base::string16(), // display_source
393 message_center::NotifierId(
394 message_center::NotifierId::SYSTEM_COMPONENT,
395 system_notifier::kNotifierDisplay),
396 message_center::RichNotificationData(),
397 new message_center::HandleNotificationClickedDelegate(
398 base::Bind(&OpenSettings))));
399
400 message_center::MessageCenter::Get()->AddNotification(notification.Pass());
401 }
402
CreateDefaultView(user::LoginStatus status)403 views::View* TrayDisplay::CreateDefaultView(user::LoginStatus status) {
404 DCHECK(default_ == NULL);
405 default_ = new DisplayView();
406 return default_;
407 }
408
DestroyDefaultView()409 void TrayDisplay::DestroyDefaultView() {
410 default_ = NULL;
411 }
412
OnDisplayConfigurationChanged()413 void TrayDisplay::OnDisplayConfigurationChanged() {
414 DisplayInfoMap old_info;
415 UpdateDisplayInfo(&old_info);
416
417 if (default_)
418 default_->Update();
419
420 if (!Shell::GetInstance()->system_tray_delegate()->
421 ShouldShowDisplayNotification()) {
422 return;
423 }
424
425 base::string16 message;
426 base::string16 additional_message;
427 if (GetDisplayMessageForNotification(old_info, &message, &additional_message))
428 CreateOrUpdateNotification(message, additional_message);
429 }
430
GetDefaultViewMessage() const431 base::string16 TrayDisplay::GetDefaultViewMessage() const {
432 if (!default_ || !default_->visible())
433 return base::string16();
434
435 return static_cast<DisplayView*>(default_)->label()->text();
436 }
437
GetAccessibleStateForTesting(ui::AXViewState * state)438 bool TrayDisplay::GetAccessibleStateForTesting(ui::AXViewState* state) {
439 views::View* view = default_;
440 if (view) {
441 view->GetAccessibleState(state);
442 return true;
443 }
444 return false;
445 }
446
447 } // namespace ash
448