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