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/chromeos/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/system/chromeos/devicetype_utils.h" | |
13 #include "ash/common/system/system_notifier.h" | |
14 #include "ash/common/system/tray/fixed_sized_image_view.h" | |
15 #include "ash/common/system/tray/system_tray_controller.h" | |
16 #include "ash/common/system/tray/system_tray_delegate.h" | |
17 #include "ash/common/system/tray/tray_constants.h" | |
18 #include "ash/common/wm_shell.h" | |
19 #include "ash/display/screen_orientation_controller_chromeos.h" | |
20 #include "ash/resources/grit/ash_resources.h" | |
21 #include "ash/shell.h" | |
22 #include "ash/strings/grit/ash_strings.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 |