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