• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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