tray_display.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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
32using message_center::Notification;
33
34namespace ash {
35namespace {
36
37DisplayManager* GetDisplayManager() {
38  return Shell::GetInstance()->display_manager();
39}
40
41base::string16 GetDisplayName(int64 display_id) {
42  return base::UTF8ToUTF16(
43      GetDisplayManager()->GetDisplayNameForId(display_id));
44}
45
46base::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"
64base::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
88base::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
110void 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_SUPERVISED:
124    case user::LOGGED_IN_KIOSK_APP:
125      ash::SystemTrayDelegate* delegate =
126          Shell::GetInstance()->system_tray_delegate();
127      if (delegate->ShouldShowSettings())
128        delegate->ShowDisplaySettings();
129  }
130}
131
132}  // namespace
133
134const char TrayDisplay::kNotificationId[] = "chrome://settings/display";
135
136class DisplayView : public ActionableView {
137 public:
138  explicit DisplayView() {
139    SetLayoutManager(new views::BoxLayout(
140        views::BoxLayout::kHorizontal,
141        kTrayPopupPaddingHorizontal, 0,
142        kTrayPopupPaddingBetweenItems));
143
144    ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
145    image_ = new FixedSizedImageView(0, kTrayPopupItemHeight);
146    image_->SetImage(
147        bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY).ToImageSkia());
148    AddChildView(image_);
149
150    label_ = new views::Label();
151    label_->SetMultiLine(true);
152    label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
153    AddChildView(label_);
154    Update();
155  }
156
157  virtual ~DisplayView() {}
158
159  void Update() {
160    base::string16 message = GetTrayDisplayMessage(NULL);
161    if (message.empty() && ShouldShowFirstDisplayInfo())
162      message = GetDisplayInfoLine(GetDisplayManager()->first_display_id());
163    SetVisible(!message.empty());
164    label_->SetText(message);
165    SetAccessibleName(message);
166    Layout();
167  }
168
169  const views::Label* label() const { return label_; }
170
171  // Overridden from views::View.
172  virtual bool GetTooltipText(const gfx::Point& p,
173                              base::string16* tooltip) const OVERRIDE {
174    base::string16 tray_message = GetTrayDisplayMessage(NULL);
175    base::string16 display_message = GetAllDisplayInfo();
176    if (tray_message.empty() && display_message.empty())
177      return false;
178
179    *tooltip = tray_message + base::ASCIIToUTF16("\n") + display_message;
180    return true;
181  }
182
183  // Returns the name of the currently connected external display.
184  // This should not be used when the external display is used for
185  // mirroring.
186  static base::string16 GetExternalDisplayName() {
187    DisplayManager* display_manager = GetDisplayManager();
188    DCHECK(!display_manager->IsMirrored());
189
190    int64 external_id = gfx::Display::kInvalidDisplayID;
191    for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
192      int64 id = display_manager->GetDisplayAt(i).id();
193      if (id != gfx::Display::InternalDisplayId()) {
194        external_id = id;
195        break;
196      }
197    }
198
199    if (external_id == gfx::Display::kInvalidDisplayID) {
200      return l10n_util::GetStringUTF16(
201          IDS_ASH_STATUS_TRAY_UNKNOWN_DISPLAY_NAME);
202    }
203
204    // The external display name may have an annotation of "(width x height)" in
205    // case that the display is rotated or its resolution is changed.
206    base::string16 name = GetDisplayName(external_id);
207    const DisplayInfo& display_info =
208        display_manager->GetDisplayInfo(external_id);
209    if (display_info.rotation() != gfx::Display::ROTATE_0 ||
210        display_info.configured_ui_scale() != 1.0f ||
211        !display_info.overscan_insets_in_dip().empty()) {
212      name = l10n_util::GetStringFUTF16(
213          IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
214          name, GetDisplaySize(external_id));
215    } else if (display_info.overscan_insets_in_dip().empty() &&
216               display_info.has_overscan()) {
217      name = l10n_util::GetStringFUTF16(
218          IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
219          name, l10n_util::GetStringUTF16(
220              IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN));
221    }
222
223    return name;
224  }
225
226  static base::string16 GetTrayDisplayMessage(
227      base::string16* additional_message_out) {
228    DisplayManager* display_manager = GetDisplayManager();
229    if (display_manager->GetNumDisplays() > 1) {
230      if (GetDisplayManager()->HasInternalDisplay()) {
231        return l10n_util::GetStringFUTF16(
232            IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetExternalDisplayName());
233      }
234      return l10n_util::GetStringUTF16(
235          IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL);
236    }
237
238    if (display_manager->IsMirrored()) {
239      if (GetDisplayManager()->HasInternalDisplay()) {
240        return l10n_util::GetStringFUTF16(
241            IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING,
242            GetDisplayName(display_manager->mirrored_display_id()));
243      }
244      return l10n_util::GetStringUTF16(
245          IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING_NO_INTERNAL);
246    }
247
248    int64 primary_id = Shell::GetScreen()->GetPrimaryDisplay().id();
249    if (display_manager->HasInternalDisplay() &&
250        !display_manager->IsInternalDisplayId(primary_id)) {
251      if (additional_message_out) {
252        *additional_message_out = l10n_util::GetStringUTF16(
253            IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED_DESCRIPTION);
254      }
255      return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED);
256    }
257
258    return base::string16();
259  }
260
261 private:
262  bool ShouldShowFirstDisplayInfo() const {
263    const DisplayInfo& display_info = GetDisplayManager()->GetDisplayInfo(
264        GetDisplayManager()->first_display_id());
265    return display_info.rotation() != gfx::Display::ROTATE_0 ||
266        display_info.configured_ui_scale() != 1.0f ||
267        !display_info.overscan_insets_in_dip().empty() ||
268        display_info.has_overscan();
269  }
270
271  // Overridden from ActionableView.
272  virtual bool PerformAction(const ui::Event& event) OVERRIDE {
273    OpenSettings();
274    return true;
275  }
276
277  virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE {
278    int label_max_width = bounds().width() - kTrayPopupPaddingHorizontal * 2 -
279        kTrayPopupPaddingBetweenItems - image_->GetPreferredSize().width();
280    label_->SizeToFit(label_max_width);
281  }
282
283  views::ImageView* image_;
284  views::Label* label_;
285
286  DISALLOW_COPY_AND_ASSIGN(DisplayView);
287};
288
289TrayDisplay::TrayDisplay(SystemTray* system_tray)
290    : SystemTrayItem(system_tray),
291      default_(NULL) {
292  Shell::GetInstance()->display_controller()->AddObserver(this);
293  UpdateDisplayInfo(NULL);
294}
295
296TrayDisplay::~TrayDisplay() {
297  Shell::GetInstance()->display_controller()->RemoveObserver(this);
298}
299
300void TrayDisplay::UpdateDisplayInfo(TrayDisplay::DisplayInfoMap* old_info) {
301  if (old_info)
302    old_info->swap(display_info_);
303  display_info_.clear();
304
305  DisplayManager* display_manager = GetDisplayManager();
306  for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
307    int64 id = display_manager->GetDisplayAt(i).id();
308    display_info_[id] = display_manager->GetDisplayInfo(id);
309  }
310}
311
312bool TrayDisplay::GetDisplayMessageForNotification(
313    const TrayDisplay::DisplayInfoMap& old_info,
314    base::string16* message_out,
315    base::string16* additional_message_out) {
316  // Display is added or removed. Use the same message as the one in
317  // the system tray.
318  if (display_info_.size() != old_info.size()) {
319    *message_out = DisplayView::GetTrayDisplayMessage(additional_message_out);
320    return true;
321  }
322
323  for (DisplayInfoMap::const_iterator iter = display_info_.begin();
324       iter != display_info_.end(); ++iter) {
325    DisplayInfoMap::const_iterator old_iter = old_info.find(iter->first);
326    // The display's number is same but different displays. This happens
327    // for the transition between docked mode and mirrored display. Falls back
328    // to GetTrayDisplayMessage().
329    if (old_iter == old_info.end()) {
330      *message_out = DisplayView::GetTrayDisplayMessage(additional_message_out);
331      return true;
332    }
333
334    if (iter->second.configured_ui_scale() !=
335        old_iter->second.configured_ui_scale()) {
336      *message_out = l10n_util::GetStringFUTF16(
337          IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED,
338          GetDisplayName(iter->first),
339          GetDisplaySize(iter->first));
340      return true;
341    }
342    if (iter->second.rotation() != old_iter->second.rotation()) {
343      int rotation_text_id = 0;
344      switch (iter->second.rotation()) {
345        case gfx::Display::ROTATE_0:
346          rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION;
347          break;
348        case gfx::Display::ROTATE_90:
349          rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90;
350          break;
351        case gfx::Display::ROTATE_180:
352          rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180;
353          break;
354        case gfx::Display::ROTATE_270:
355          rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270;
356          break;
357      }
358      *message_out = l10n_util::GetStringFUTF16(
359          IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED,
360          GetDisplayName(iter->first),
361          l10n_util::GetStringUTF16(rotation_text_id));
362      return true;
363    }
364  }
365
366  // Found nothing special
367  return false;
368}
369
370void TrayDisplay::CreateOrUpdateNotification(
371    const base::string16& message,
372    const base::string16& additional_message) {
373  // Always remove the notification to make sure the notification appears
374  // as a popup in any situation.
375  message_center::MessageCenter::Get()->RemoveNotification(
376      kNotificationId, false /* by_user */);
377
378  if (message.empty())
379    return;
380
381  // Don't display notifications for accelerometer triggered screen rotations.
382  // See http://crbug.com/364949
383  if (Shell::GetInstance()->maximize_mode_controller()->
384      in_set_screen_rotation()) {
385    return;
386  }
387
388  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
389  scoped_ptr<Notification> notification(new Notification(
390      message_center::NOTIFICATION_TYPE_SIMPLE,
391      kNotificationId,
392      message,
393      additional_message,
394      bundle.GetImageNamed(IDR_AURA_NOTIFICATION_DISPLAY),
395      base::string16(),  // display_source
396      message_center::NotifierId(
397          message_center::NotifierId::SYSTEM_COMPONENT,
398          system_notifier::kNotifierDisplay),
399      message_center::RichNotificationData(),
400      new message_center::HandleNotificationClickedDelegate(
401          base::Bind(&OpenSettings))));
402
403    message_center::MessageCenter::Get()->AddNotification(notification.Pass());
404}
405
406views::View* TrayDisplay::CreateDefaultView(user::LoginStatus status) {
407  DCHECK(default_ == NULL);
408  default_ = new DisplayView();
409  return default_;
410}
411
412void TrayDisplay::DestroyDefaultView() {
413  default_ = NULL;
414}
415
416void TrayDisplay::OnDisplayConfigurationChanged() {
417  DisplayInfoMap old_info;
418  UpdateDisplayInfo(&old_info);
419
420  if (default_)
421    default_->Update();
422
423  if (!Shell::GetInstance()->system_tray_delegate()->
424          ShouldShowDisplayNotification()) {
425    return;
426  }
427
428  base::string16 message;
429  base::string16 additional_message;
430  if (GetDisplayMessageForNotification(old_info, &message, &additional_message))
431    CreateOrUpdateNotification(message, additional_message);
432}
433
434base::string16 TrayDisplay::GetDefaultViewMessage() const {
435  if (!default_ || !default_->visible())
436    return base::string16();
437
438  return static_cast<DisplayView*>(default_)->label()->text();
439}
440
441bool TrayDisplay::GetAccessibleStateForTesting(ui::AXViewState* state) {
442  views::View* view = default_;
443  if (view) {
444    view->GetAccessibleState(state);
445    return true;
446  }
447  return false;
448}
449
450}  // namespace ash
451