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