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 37 DisplayManager* GetDisplayManager() { 38 return Shell::GetInstance()->display_manager(); 39 } 40 41 base::string16 GetDisplayName(int64 display_id) { 42 return base::UTF8ToUTF16( 43 GetDisplayManager()->GetDisplayNameForId(display_id)); 44 } 45 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" 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 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 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_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 134 const char TrayDisplay::kNotificationId[] = "chrome://settings/display"; 135 136 class 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 289 TrayDisplay::TrayDisplay(SystemTray* system_tray) 290 : SystemTrayItem(system_tray), 291 default_(NULL) { 292 Shell::GetInstance()->display_controller()->AddObserver(this); 293 UpdateDisplayInfo(NULL); 294 } 295 296 TrayDisplay::~TrayDisplay() { 297 Shell::GetInstance()->display_controller()->RemoveObserver(this); 298 } 299 300 void 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 312 bool 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 370 void 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 ignore_display_configuration_updates()) { 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 406 views::View* TrayDisplay::CreateDefaultView(user::LoginStatus status) { 407 DCHECK(default_ == NULL); 408 default_ = new DisplayView(); 409 return default_; 410 } 411 412 void TrayDisplay::DestroyDefaultView() { 413 default_ = NULL; 414 } 415 416 void 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 434 base::string16 TrayDisplay::GetDefaultViewMessage() const { 435 if (!default_ || !default_->visible()) 436 return base::string16(); 437 438 return static_cast<DisplayView*>(default_)->label()->text(); 439 } 440 441 bool 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