Fix keyboard accessibility feedback
[chromium-blink-merge.git] / ash / system / chromeos / tray_display.cc
blob9419b7759e24cb20c23c54350d379e57d0814455
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"
7 #include <vector>
9 #include "ash/content/display/screen_orientation_controller_chromeos.h"
10 #include "ash/display/display_controller.h"
11 #include "ash/display/display_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;
37 namespace ash {
38 namespace {
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;
57 DCHECK(!mirroring);
58 if (mirroring)
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,
78 size_text,
79 l10n_util::GetStringUTF16(
80 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN));
81 } else {
82 display_data = size_text;
85 return l10n_util::GetStringFUTF16(
86 IDS_ASH_STATUS_TRAY_DISPLAY_SINGLE_DISPLAY,
87 GetDisplayName(display_id),
88 display_data);
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::HasInternalDisplay() &&
98 gfx::Display::InternalDisplayId() ==
99 display_manager->first_display_id()) {
100 internal_id = display_manager->first_display_id();
101 lines.push_back(GetDisplayInfoLine(internal_id));
104 for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
105 int64 id = display_manager->GetDisplayAt(i).id();
106 if (id == internal_id)
107 continue;
108 lines.push_back(GetDisplayInfoLine(id));
111 return JoinString(lines, '\n');
114 void OpenSettings() {
115 // switch is intentionally introduced without default, to cause an error when
116 // a new type of login status is introduced.
117 switch (Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus()) {
118 case user::LOGGED_IN_NONE:
119 case user::LOGGED_IN_LOCKED:
120 return;
122 case user::LOGGED_IN_USER:
123 case user::LOGGED_IN_OWNER:
124 case user::LOGGED_IN_GUEST:
125 case user::LOGGED_IN_PUBLIC:
126 case user::LOGGED_IN_SUPERVISED:
127 case user::LOGGED_IN_KIOSK_APP:
128 ash::SystemTrayDelegate* delegate =
129 Shell::GetInstance()->system_tray_delegate();
130 if (delegate->ShouldShowSettings())
131 delegate->ShowDisplaySettings();
135 } // namespace
137 const char TrayDisplay::kNotificationId[] = "chrome://settings/display";
139 class DisplayView : public ActionableView {
140 public:
141 explicit DisplayView() {
142 SetLayoutManager(new views::BoxLayout(
143 views::BoxLayout::kHorizontal,
144 kTrayPopupPaddingHorizontal, 0,
145 kTrayPopupPaddingBetweenItems));
147 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
148 image_ = new FixedSizedImageView(0, kTrayPopupItemHeight);
149 image_->SetImage(
150 bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY).ToImageSkia());
151 AddChildView(image_);
153 label_ = new views::Label();
154 label_->SetMultiLine(true);
155 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
156 AddChildView(label_);
157 Update();
160 ~DisplayView() override {}
162 void Update() {
163 base::string16 message = GetTrayDisplayMessage(NULL);
164 if (message.empty() && ShouldShowFirstDisplayInfo())
165 message = GetDisplayInfoLine(GetDisplayManager()->first_display_id());
166 SetVisible(!message.empty());
167 label_->SetText(message);
168 SetAccessibleName(message);
169 Layout();
172 const views::Label* label() const { return label_; }
174 // Overridden from views::View.
175 bool GetTooltipText(const gfx::Point& p,
176 base::string16* tooltip) const override {
177 base::string16 tray_message = GetTrayDisplayMessage(NULL);
178 base::string16 display_message = GetAllDisplayInfo();
179 if (tray_message.empty() && display_message.empty())
180 return false;
182 *tooltip = tray_message + base::ASCIIToUTF16("\n") + display_message;
183 return true;
186 // Returns the name of the currently connected external display.
187 // This should not be used when the external display is used for
188 // mirroring.
189 static base::string16 GetExternalDisplayName() {
190 DisplayManager* display_manager = GetDisplayManager();
191 DCHECK(!display_manager->IsInMirrorMode());
193 int64 external_id = gfx::Display::kInvalidDisplayID;
194 for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
195 int64 id = display_manager->GetDisplayAt(i).id();
196 if (id != gfx::Display::InternalDisplayId()) {
197 external_id = id;
198 break;
202 if (external_id == gfx::Display::kInvalidDisplayID) {
203 return l10n_util::GetStringUTF16(
204 IDS_ASH_STATUS_TRAY_UNKNOWN_DISPLAY_NAME);
207 // The external display name may have an annotation of "(width x height)" in
208 // case that the display is rotated or its resolution is changed.
209 base::string16 name = GetDisplayName(external_id);
210 const DisplayInfo& display_info =
211 display_manager->GetDisplayInfo(external_id);
212 if (display_info.GetActiveRotation() != gfx::Display::ROTATE_0 ||
213 display_info.configured_ui_scale() != 1.0f ||
214 !display_info.overscan_insets_in_dip().empty()) {
215 name = l10n_util::GetStringFUTF16(
216 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
217 name, GetDisplaySize(external_id));
218 } else if (display_info.overscan_insets_in_dip().empty() &&
219 display_info.has_overscan()) {
220 name = l10n_util::GetStringFUTF16(
221 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
222 name, l10n_util::GetStringUTF16(
223 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN));
226 return name;
229 static base::string16 GetTrayDisplayMessage(
230 base::string16* additional_message_out) {
231 DisplayManager* display_manager = GetDisplayManager();
232 if (display_manager->GetNumDisplays() > 1) {
233 if (gfx::Display::HasInternalDisplay()) {
234 return l10n_util::GetStringFUTF16(
235 IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetExternalDisplayName());
237 return l10n_util::GetStringUTF16(
238 IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL);
241 if (display_manager->IsInMirrorMode()) {
242 if (gfx::Display::HasInternalDisplay()) {
243 return l10n_util::GetStringFUTF16(
244 IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING,
245 GetDisplayName(display_manager->mirroring_display_id()));
247 return l10n_util::GetStringUTF16(
248 IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING_NO_INTERNAL);
251 if (display_manager->IsInUnifiedMode())
252 return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_UNIFIED);
254 int64 primary_id = Shell::GetScreen()->GetPrimaryDisplay().id();
255 if (gfx::Display::HasInternalDisplay() &&
256 !(gfx::Display::InternalDisplayId() == primary_id)) {
257 if (additional_message_out) {
258 *additional_message_out = ash::SubstituteChromeOSDeviceType(
259 IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED_DESCRIPTION);
261 return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED);
264 return base::string16();
267 private:
268 bool ShouldShowFirstDisplayInfo() const {
269 const DisplayInfo& display_info = GetDisplayManager()->GetDisplayInfo(
270 GetDisplayManager()->first_display_id());
271 return display_info.GetActiveRotation() != gfx::Display::ROTATE_0 ||
272 display_info.configured_ui_scale() != 1.0f ||
273 !display_info.overscan_insets_in_dip().empty() ||
274 display_info.has_overscan();
277 // Overridden from ActionableView.
278 bool PerformAction(const ui::Event& event) override {
279 OpenSettings();
280 return true;
283 void OnBoundsChanged(const gfx::Rect& previous_bounds) override {
284 int label_max_width = bounds().width() - kTrayPopupPaddingHorizontal * 2 -
285 kTrayPopupPaddingBetweenItems - image_->GetPreferredSize().width();
286 label_->SizeToFit(label_max_width);
289 views::ImageView* image_;
290 views::Label* label_;
292 DISALLOW_COPY_AND_ASSIGN(DisplayView);
295 TrayDisplay::TrayDisplay(SystemTray* system_tray)
296 : SystemTrayItem(system_tray),
297 default_(NULL) {
298 Shell::GetInstance()->display_controller()->AddObserver(this);
299 UpdateDisplayInfo(NULL);
302 TrayDisplay::~TrayDisplay() {
303 Shell::GetInstance()->display_controller()->RemoveObserver(this);
306 void TrayDisplay::UpdateDisplayInfo(TrayDisplay::DisplayInfoMap* old_info) {
307 if (old_info)
308 old_info->swap(display_info_);
309 display_info_.clear();
311 DisplayManager* display_manager = GetDisplayManager();
312 for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
313 int64 id = display_manager->GetDisplayAt(i).id();
314 display_info_[id] = display_manager->GetDisplayInfo(id);
318 bool TrayDisplay::GetDisplayMessageForNotification(
319 const TrayDisplay::DisplayInfoMap& old_info,
320 base::string16* message_out,
321 base::string16* additional_message_out) {
322 // Display is added or removed. Use the same message as the one in
323 // the system tray.
324 if (display_info_.size() != old_info.size()) {
325 *message_out = DisplayView::GetTrayDisplayMessage(additional_message_out);
326 return true;
329 for (DisplayInfoMap::const_iterator iter = display_info_.begin();
330 iter != display_info_.end(); ++iter) {
331 DisplayInfoMap::const_iterator old_iter = old_info.find(iter->first);
332 // The display's number is same but different displays. This happens
333 // for the transition between docked mode and mirrored display. Falls back
334 // to GetTrayDisplayMessage().
335 if (old_iter == old_info.end()) {
336 *message_out = DisplayView::GetTrayDisplayMessage(additional_message_out);
337 return true;
340 if (iter->second.configured_ui_scale() !=
341 old_iter->second.configured_ui_scale()) {
342 *additional_message_out = l10n_util::GetStringFUTF16(
343 IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED,
344 GetDisplayName(iter->first), GetDisplaySize(iter->first));
345 return true;
347 if (iter->second.GetActiveRotation() !=
348 old_iter->second.GetActiveRotation()) {
349 int rotation_text_id = 0;
350 switch (iter->second.GetActiveRotation()) {
351 case gfx::Display::ROTATE_0:
352 rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION;
353 break;
354 case gfx::Display::ROTATE_90:
355 rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90;
356 break;
357 case gfx::Display::ROTATE_180:
358 rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180;
359 break;
360 case gfx::Display::ROTATE_270:
361 rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270;
362 break;
364 *additional_message_out = l10n_util::GetStringFUTF16(
365 IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetDisplayName(iter->first),
366 l10n_util::GetStringUTF16(rotation_text_id));
367 return true;
371 // Found nothing special
372 return false;
375 void TrayDisplay::CreateOrUpdateNotification(
376 const base::string16& message,
377 const base::string16& additional_message) {
378 // Always remove the notification to make sure the notification appears
379 // as a popup in any situation.
380 message_center::MessageCenter::Get()->RemoveNotification(
381 kNotificationId, false /* by_user */);
383 if (message.empty() && additional_message.empty())
384 return;
386 // Don't display notifications for accelerometer triggered screen rotations.
387 // See http://crbug.com/364949
388 if (Shell::GetInstance()
389 ->screen_orientation_controller()
390 ->ignore_display_configuration_updates()) {
391 return;
394 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
395 scoped_ptr<Notification> notification(new Notification(
396 message_center::NOTIFICATION_TYPE_SIMPLE,
397 kNotificationId,
398 message,
399 additional_message,
400 bundle.GetImageNamed(IDR_AURA_NOTIFICATION_DISPLAY),
401 base::string16(), // display_source
402 message_center::NotifierId(
403 message_center::NotifierId::SYSTEM_COMPONENT,
404 system_notifier::kNotifierDisplay),
405 message_center::RichNotificationData(),
406 new message_center::HandleNotificationClickedDelegate(
407 base::Bind(&OpenSettings))));
409 message_center::MessageCenter::Get()->AddNotification(notification.Pass());
412 views::View* TrayDisplay::CreateDefaultView(user::LoginStatus status) {
413 DCHECK(default_ == NULL);
414 default_ = new DisplayView();
415 return default_;
418 void TrayDisplay::DestroyDefaultView() {
419 default_ = NULL;
422 void TrayDisplay::OnDisplayConfigurationChanged() {
423 DisplayInfoMap old_info;
424 UpdateDisplayInfo(&old_info);
426 if (default_)
427 default_->Update();
429 if (!Shell::GetInstance()->system_tray_delegate()->
430 ShouldShowDisplayNotification()) {
431 return;
434 base::string16 message;
435 base::string16 additional_message;
436 if (GetDisplayMessageForNotification(old_info, &message, &additional_message))
437 CreateOrUpdateNotification(message, additional_message);
440 base::string16 TrayDisplay::GetDefaultViewMessage() const {
441 if (!default_ || !default_->visible())
442 return base::string16();
444 return static_cast<DisplayView*>(default_)->label()->text();
447 bool TrayDisplay::GetAccessibleStateForTesting(ui::AXViewState* state) {
448 views::View* view = default_;
449 if (view) {
450 view->GetAccessibleState(state);
451 return true;
453 return false;
456 } // namespace ash