| OLD | NEW |
| (Empty) |
| 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/screen_layout_observer.h" | |
| 6 | |
| 7 #include <memory> | |
| 8 #include <utility> | |
| 9 #include <vector> | |
| 10 | |
| 11 #include "ash/common/metrics/user_metrics_action.h" | |
| 12 #include "ash/common/wm_shell.h" | |
| 13 #include "ash/display/screen_orientation_controller_chromeos.h" | |
| 14 #include "ash/resources/grit/ash_resources.h" | |
| 15 #include "ash/shell.h" | |
| 16 #include "ash/strings/grit/ash_strings.h" | |
| 17 #include "ash/system/devicetype_utils.h" | |
| 18 #include "ash/system/system_notifier.h" | |
| 19 #include "ash/system/tray/fixed_sized_image_view.h" | |
| 20 #include "ash/system/tray/system_tray_controller.h" | |
| 21 #include "ash/system/tray/system_tray_delegate.h" | |
| 22 #include "ash/system/tray/tray_constants.h" | |
| 23 #include "base/bind.h" | |
| 24 #include "base/strings/string_util.h" | |
| 25 #include "base/strings/utf_string_conversions.h" | |
| 26 #include "ui/base/l10n/l10n_util.h" | |
| 27 #include "ui/base/resource/resource_bundle.h" | |
| 28 #include "ui/display/display.h" | |
| 29 #include "ui/display/manager/display_manager.h" | |
| 30 #include "ui/display/types/display_constants.h" | |
| 31 #include "ui/message_center/message_center.h" | |
| 32 #include "ui/message_center/notification.h" | |
| 33 #include "ui/message_center/notification_delegate.h" | |
| 34 #include "ui/strings/grit/ui_strings.h" | |
| 35 | |
| 36 using message_center::Notification; | |
| 37 | |
| 38 namespace ash { | |
| 39 namespace { | |
| 40 | |
| 41 display::DisplayManager* GetDisplayManager() { | |
| 42 return Shell::GetInstance()->display_manager(); | |
| 43 } | |
| 44 | |
| 45 base::string16 GetDisplayName(int64_t display_id) { | |
| 46 return base::UTF8ToUTF16( | |
| 47 GetDisplayManager()->GetDisplayNameForId(display_id)); | |
| 48 } | |
| 49 | |
| 50 base::string16 GetDisplaySize(int64_t display_id) { | |
| 51 display::DisplayManager* display_manager = GetDisplayManager(); | |
| 52 | |
| 53 const display::Display* display = | |
| 54 &display_manager->GetDisplayForId(display_id); | |
| 55 | |
| 56 // We don't show display size for mirrored display. Fallback | |
| 57 // to empty string if this happens on release build. | |
| 58 bool mirroring = display_manager->mirroring_display_id() == display_id; | |
| 59 DCHECK(!mirroring); | |
| 60 if (mirroring) | |
| 61 return base::string16(); | |
| 62 | |
| 63 DCHECK(display->is_valid()); | |
| 64 return base::UTF8ToUTF16(display->size().ToString()); | |
| 65 } | |
| 66 | |
| 67 // Attempts to open the display settings, returns true if successful. | |
| 68 bool OpenSettings() { | |
| 69 // switch is intentionally introduced without default, to cause an error when | |
| 70 // a new type of login status is introduced. | |
| 71 switch (WmShell::Get()->system_tray_delegate()->GetUserLoginStatus()) { | |
| 72 case LoginStatus::NOT_LOGGED_IN: | |
| 73 case LoginStatus::LOCKED: | |
| 74 return false; | |
| 75 | |
| 76 case LoginStatus::USER: | |
| 77 case LoginStatus::OWNER: | |
| 78 case LoginStatus::GUEST: | |
| 79 case LoginStatus::PUBLIC: | |
| 80 case LoginStatus::SUPERVISED: | |
| 81 case LoginStatus::KIOSK_APP: | |
| 82 case LoginStatus::ARC_KIOSK_APP: | |
| 83 SystemTrayDelegate* delegate = WmShell::Get()->system_tray_delegate(); | |
| 84 if (delegate->ShouldShowSettings()) { | |
| 85 WmShell::Get()->system_tray_controller()->ShowDisplaySettings(); | |
| 86 return true; | |
| 87 } | |
| 88 break; | |
| 89 } | |
| 90 | |
| 91 return false; | |
| 92 } | |
| 93 | |
| 94 // Callback to handle a user selecting the notification view. | |
| 95 void OpenSettingsFromNotification() { | |
| 96 WmShell::Get()->RecordUserMetricsAction( | |
| 97 UMA_STATUS_AREA_DISPLAY_NOTIFICATION_SELECTED); | |
| 98 if (OpenSettings()) { | |
| 99 WmShell::Get()->RecordUserMetricsAction( | |
| 100 UMA_STATUS_AREA_DISPLAY_NOTIFICATION_SHOW_SETTINGS); | |
| 101 } | |
| 102 } | |
| 103 | |
| 104 // Returns the name of the currently connected external display whose ID is | |
| 105 // |external_display_id|. This should not be used when the external display is | |
| 106 // used for mirroring. | |
| 107 base::string16 GetExternalDisplayName(int64_t external_display_id) { | |
| 108 DCHECK(!display::Display::IsInternalDisplayId(external_display_id)); | |
| 109 | |
| 110 display::DisplayManager* display_manager = GetDisplayManager(); | |
| 111 DCHECK(!display_manager->IsInMirrorMode()); | |
| 112 | |
| 113 if (external_display_id == display::kInvalidDisplayId) | |
| 114 return l10n_util::GetStringUTF16(IDS_DISPLAY_NAME_UNKNOWN); | |
| 115 | |
| 116 // The external display name may have an annotation of "(width x height)" in | |
| 117 // case that the display is rotated or its resolution is changed. | |
| 118 base::string16 name = GetDisplayName(external_display_id); | |
| 119 const display::ManagedDisplayInfo& display_info = | |
| 120 display_manager->GetDisplayInfo(external_display_id); | |
| 121 if (display_info.GetActiveRotation() != display::Display::ROTATE_0 || | |
| 122 display_info.configured_ui_scale() != 1.0f || | |
| 123 !display_info.overscan_insets_in_dip().IsEmpty()) { | |
| 124 name = | |
| 125 l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME, | |
| 126 name, GetDisplaySize(external_display_id)); | |
| 127 } else if (display_info.overscan_insets_in_dip().IsEmpty() && | |
| 128 display_info.has_overscan()) { | |
| 129 name = l10n_util::GetStringFUTF16( | |
| 130 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME, name, | |
| 131 l10n_util::GetStringUTF16( | |
| 132 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN)); | |
| 133 } | |
| 134 | |
| 135 return name; | |
| 136 } | |
| 137 | |
| 138 // Returns true if docked mode is currently enabled. | |
| 139 bool IsDockedModeEnabled() { | |
| 140 display::DisplayManager* display_manager = GetDisplayManager(); | |
| 141 if (!display::Display::HasInternalDisplay()) | |
| 142 return false; | |
| 143 | |
| 144 for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) { | |
| 145 if (display::Display::IsInternalDisplayId( | |
| 146 display_manager->GetDisplayAt(i).id())) { | |
| 147 return false; | |
| 148 } | |
| 149 } | |
| 150 | |
| 151 // We have an internal display but it's not one of the active displays. | |
| 152 return true; | |
| 153 } | |
| 154 | |
| 155 // Returns the notification message that should be shown when mirror display | |
| 156 // mode is entered. | |
| 157 base::string16 GetEnterMirrorModeMessage() { | |
| 158 if (display::Display::HasInternalDisplay()) { | |
| 159 return l10n_util::GetStringFUTF16( | |
| 160 IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, | |
| 161 GetDisplayName(GetDisplayManager()->mirroring_display_id())); | |
| 162 } | |
| 163 | |
| 164 return l10n_util::GetStringUTF16( | |
| 165 IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING_NO_INTERNAL); | |
| 166 } | |
| 167 | |
| 168 // Returns the notification message that should be shown when unified desktop | |
| 169 // mode is entered. | |
| 170 base::string16 GetEnterUnifiedModeMessage() { | |
| 171 return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_UNIFIED); | |
| 172 } | |
| 173 | |
| 174 // Returns the notification message that should be shown when unified desktop | |
| 175 // mode is exited. | |
| 176 base::string16 GetExitUnifiedModeMessage() { | |
| 177 return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_UNIFIED_EXITING); | |
| 178 } | |
| 179 | |
| 180 base::string16 GetDisplayRemovedMessage( | |
| 181 const display::ManagedDisplayInfo& removed_display_info, | |
| 182 base::string16* out_additional_message) { | |
| 183 return l10n_util::GetStringFUTF16( | |
| 184 IDS_ASH_STATUS_TRAY_DISPLAY_REMOVED, | |
| 185 base::UTF8ToUTF16(removed_display_info.name())); | |
| 186 } | |
| 187 | |
| 188 base::string16 GetDisplayAddedMessage(int64_t added_display_id, | |
| 189 base::string16* additional_message_out) { | |
| 190 if (!display::Display::HasInternalDisplay()) { | |
| 191 return l10n_util::GetStringUTF16( | |
| 192 IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL); | |
| 193 } | |
| 194 | |
| 195 return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, | |
| 196 GetExternalDisplayName(added_display_id)); | |
| 197 } | |
| 198 | |
| 199 } // namespace | |
| 200 | |
| 201 const char ScreenLayoutObserver::kNotificationId[] = | |
| 202 "chrome://settings/display"; | |
| 203 | |
| 204 ScreenLayoutObserver::ScreenLayoutObserver() { | |
| 205 WmShell::Get()->AddDisplayObserver(this); | |
| 206 UpdateDisplayInfo(NULL); | |
| 207 } | |
| 208 | |
| 209 ScreenLayoutObserver::~ScreenLayoutObserver() { | |
| 210 WmShell::Get()->RemoveDisplayObserver(this); | |
| 211 } | |
| 212 | |
| 213 void ScreenLayoutObserver::UpdateDisplayInfo( | |
| 214 ScreenLayoutObserver::DisplayInfoMap* old_info) { | |
| 215 if (old_info) | |
| 216 old_info->swap(display_info_); | |
| 217 display_info_.clear(); | |
| 218 | |
| 219 display::DisplayManager* display_manager = GetDisplayManager(); | |
| 220 for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) { | |
| 221 int64_t id = display_manager->GetDisplayAt(i).id(); | |
| 222 display_info_[id] = display_manager->GetDisplayInfo(id); | |
| 223 } | |
| 224 } | |
| 225 | |
| 226 bool ScreenLayoutObserver::GetDisplayMessageForNotification( | |
| 227 const ScreenLayoutObserver::DisplayInfoMap& old_info, | |
| 228 base::string16* out_message, | |
| 229 base::string16* out_additional_message) { | |
| 230 if (old_display_mode_ != current_display_mode_) { | |
| 231 // Detect changes in the mirror mode status. | |
| 232 if (current_display_mode_ == DisplayMode::MIRRORING) { | |
| 233 *out_message = GetEnterMirrorModeMessage(); | |
| 234 return true; | |
| 235 } | |
| 236 if (old_display_mode_ == DisplayMode::MIRRORING && | |
| 237 GetExitMirrorModeMessage(out_message, out_additional_message)) { | |
| 238 return true; | |
| 239 } | |
| 240 | |
| 241 // Detect changes in the unified mode status. | |
| 242 if (current_display_mode_ == DisplayMode::UNIFIED) { | |
| 243 *out_message = GetEnterUnifiedModeMessage(); | |
| 244 return true; | |
| 245 } | |
| 246 if (old_display_mode_ == DisplayMode::UNIFIED) { | |
| 247 *out_message = GetExitUnifiedModeMessage(); | |
| 248 return true; | |
| 249 } | |
| 250 | |
| 251 if (current_display_mode_ == DisplayMode::DOCKED || | |
| 252 old_display_mode_ == DisplayMode::DOCKED) { | |
| 253 // We no longer show any notification for docked mode events. | |
| 254 // crbug.com/674719. | |
| 255 return false; | |
| 256 } | |
| 257 } | |
| 258 | |
| 259 // Displays are added or removed. | |
| 260 if (display_info_.size() < old_info.size()) { | |
| 261 // A display has been removed. | |
| 262 for (const auto& iter : old_info) { | |
| 263 if (display_info_.count(iter.first)) | |
| 264 continue; | |
| 265 | |
| 266 *out_message = | |
| 267 GetDisplayRemovedMessage(iter.second, out_additional_message); | |
| 268 return true; | |
| 269 } | |
| 270 } else if (display_info_.size() > old_info.size()) { | |
| 271 // A display has been added. | |
| 272 for (const auto& iter : display_info_) { | |
| 273 if (old_info.count(iter.first)) | |
| 274 continue; | |
| 275 | |
| 276 *out_message = GetDisplayAddedMessage(iter.first, out_additional_message); | |
| 277 return true; | |
| 278 } | |
| 279 } | |
| 280 | |
| 281 for (const auto& iter : display_info_) { | |
| 282 DisplayInfoMap::const_iterator old_iter = old_info.find(iter.first); | |
| 283 if (old_iter == old_info.end()) { | |
| 284 // The display's number is same but different displays. This happens | |
| 285 // for the transition between docked mode and mirrored display. | |
| 286 // This condition can never be reached here, since it is handled above. | |
| 287 NOTREACHED() << "A display mode transition that should have been handled" | |
| 288 "earlier."; | |
| 289 return false; | |
| 290 } | |
| 291 | |
| 292 if (iter.second.configured_ui_scale() != | |
| 293 old_iter->second.configured_ui_scale()) { | |
| 294 *out_additional_message = l10n_util::GetStringFUTF16( | |
| 295 IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED, | |
| 296 GetDisplayName(iter.first), GetDisplaySize(iter.first)); | |
| 297 return true; | |
| 298 } | |
| 299 if (iter.second.GetActiveRotation() != | |
| 300 old_iter->second.GetActiveRotation()) { | |
| 301 int rotation_text_id = 0; | |
| 302 switch (iter.second.GetActiveRotation()) { | |
| 303 case display::Display::ROTATE_0: | |
| 304 rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION; | |
| 305 break; | |
| 306 case display::Display::ROTATE_90: | |
| 307 rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90; | |
| 308 break; | |
| 309 case display::Display::ROTATE_180: | |
| 310 rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180; | |
| 311 break; | |
| 312 case display::Display::ROTATE_270: | |
| 313 rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270; | |
| 314 break; | |
| 315 } | |
| 316 *out_additional_message = l10n_util::GetStringFUTF16( | |
| 317 IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetDisplayName(iter.first), | |
| 318 l10n_util::GetStringUTF16(rotation_text_id)); | |
| 319 return true; | |
| 320 } | |
| 321 } | |
| 322 | |
| 323 // Found nothing special | |
| 324 return false; | |
| 325 } | |
| 326 | |
| 327 void ScreenLayoutObserver::CreateOrUpdateNotification( | |
| 328 const base::string16& message, | |
| 329 const base::string16& additional_message) { | |
| 330 // Always remove the notification to make sure the notification appears | |
| 331 // as a popup in any situation. | |
| 332 message_center::MessageCenter::Get()->RemoveNotification(kNotificationId, | |
| 333 false /* by_user */); | |
| 334 | |
| 335 if (message.empty() && additional_message.empty()) | |
| 336 return; | |
| 337 | |
| 338 // Don't display notifications for accelerometer triggered screen rotations. | |
| 339 // See http://crbug.com/364949 | |
| 340 if (Shell::GetInstance() | |
| 341 ->screen_orientation_controller() | |
| 342 ->ignore_display_configuration_updates()) { | |
| 343 return; | |
| 344 } | |
| 345 | |
| 346 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); | |
| 347 std::unique_ptr<Notification> notification(new Notification( | |
| 348 message_center::NOTIFICATION_TYPE_SIMPLE, kNotificationId, message, | |
| 349 additional_message, bundle.GetImageNamed(IDR_AURA_NOTIFICATION_DISPLAY), | |
| 350 base::string16(), // display_source | |
| 351 GURL(), | |
| 352 message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT, | |
| 353 system_notifier::kNotifierDisplay), | |
| 354 message_center::RichNotificationData(), | |
| 355 new message_center::HandleNotificationClickedDelegate( | |
| 356 base::Bind(&OpenSettingsFromNotification)))); | |
| 357 | |
| 358 WmShell::Get()->RecordUserMetricsAction( | |
| 359 UMA_STATUS_AREA_DISPLAY_NOTIFICATION_CREATED); | |
| 360 message_center::MessageCenter::Get()->AddNotification( | |
| 361 std::move(notification)); | |
| 362 } | |
| 363 | |
| 364 void ScreenLayoutObserver::OnDisplayConfigurationChanged() { | |
| 365 DisplayInfoMap old_info; | |
| 366 UpdateDisplayInfo(&old_info); | |
| 367 | |
| 368 old_display_mode_ = current_display_mode_; | |
| 369 if (GetDisplayManager()->IsInMirrorMode()) | |
| 370 current_display_mode_ = DisplayMode::MIRRORING; | |
| 371 else if (GetDisplayManager()->IsInUnifiedMode()) | |
| 372 current_display_mode_ = DisplayMode::UNIFIED; | |
| 373 else if (IsDockedModeEnabled()) | |
| 374 current_display_mode_ = DisplayMode::DOCKED; | |
| 375 else if (GetDisplayManager()->GetNumDisplays() > 2) | |
| 376 current_display_mode_ = DisplayMode::EXTENDED_3_PLUS; | |
| 377 else if (GetDisplayManager()->GetNumDisplays() == 2) | |
| 378 current_display_mode_ = DisplayMode::EXTENDED_2; | |
| 379 else | |
| 380 current_display_mode_ = DisplayMode::SINGLE; | |
| 381 | |
| 382 if (!show_notifications_for_testing) | |
| 383 return; | |
| 384 | |
| 385 base::string16 message; | |
| 386 base::string16 additional_message; | |
| 387 if (GetDisplayMessageForNotification(old_info, &message, &additional_message)) | |
| 388 CreateOrUpdateNotification(message, additional_message); | |
| 389 } | |
| 390 | |
| 391 bool ScreenLayoutObserver::GetExitMirrorModeMessage( | |
| 392 base::string16* out_message, | |
| 393 base::string16* out_additional_message) { | |
| 394 switch (current_display_mode_) { | |
| 395 case DisplayMode::EXTENDED_3_PLUS: | |
| 396 // Mirror mode was turned off due to having more than two displays. | |
| 397 // Show a message that mirror mode for 3+ displays is not supported. | |
| 398 *out_message = | |
| 399 l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_MIRRORING_NOT_SUPPORTED); | |
| 400 return true; | |
| 401 | |
| 402 case DisplayMode::DOCKED: | |
| 403 // Handle disabling mirror mode as a result of going to docked mode | |
| 404 // when we only have a single display (this means we actually have two | |
| 405 // physical displays, one of which is the internal display, but they | |
| 406 // were in mirror mode, and hence considered as one. Closing the | |
| 407 // internal display disables mirror mode and we still have a single | |
| 408 // active display). | |
| 409 // Falls through. | |
| 410 case DisplayMode::SINGLE: | |
| 411 // We're exiting mirror mode because we removed one of the two | |
| 412 // displays. | |
| 413 *out_message = | |
| 414 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_MIRROR_EXIT); | |
| 415 return true; | |
| 416 | |
| 417 default: | |
| 418 // Mirror mode was turned off; other messages should be shown e.g. | |
| 419 // extended mode is on, ... etc. | |
| 420 return false; | |
| 421 } | |
| 422 } | |
| 423 | |
| 424 } // namespace ash | |
| OLD | NEW |