1 // Copyright 2013 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/display/resolution_notification_controller.h" 6 7 #include "ash/display/display_controller.h" 8 #include "ash/display/display_info.h" 9 #include "ash/display/display_manager.h" 10 #include "ash/shell.h" 11 #include "ash/system/system_notifier.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "grit/ash_resources.h" 14 #include "grit/ash_strings.h" 15 #include "ui/base/l10n/l10n_util.h" 16 #include "ui/base/l10n/time_format.h" 17 #include "ui/base/resource/resource_bundle.h" 18 #include "ui/gfx/display.h" 19 #include "ui/gfx/screen.h" 20 #include "ui/message_center/message_center.h" 21 #include "ui/message_center/notification.h" 22 #include "ui/message_center/notification_delegate.h" 23 24 using message_center::Notification; 25 26 namespace ash { 27 namespace { 28 29 bool g_use_timer = true; 30 31 class ResolutionChangeNotificationDelegate 32 : public message_center::NotificationDelegate { 33 public: 34 ResolutionChangeNotificationDelegate( 35 ResolutionNotificationController* controller, 36 bool has_timeout); 37 38 protected: 39 virtual ~ResolutionChangeNotificationDelegate(); 40 41 private: 42 // message_center::NotificationDelegate overrides: 43 virtual void Display() OVERRIDE; 44 virtual void Error() OVERRIDE; 45 virtual void Close(bool by_user) OVERRIDE; 46 virtual void Click() OVERRIDE; 47 virtual bool HasClickedListener() OVERRIDE; 48 virtual void ButtonClick(int button_index) OVERRIDE; 49 50 ResolutionNotificationController* controller_; 51 bool has_timeout_; 52 53 DISALLOW_COPY_AND_ASSIGN(ResolutionChangeNotificationDelegate); 54 }; 55 56 ResolutionChangeNotificationDelegate::ResolutionChangeNotificationDelegate( 57 ResolutionNotificationController* controller, 58 bool has_timeout) 59 : controller_(controller), 60 has_timeout_(has_timeout) { 61 DCHECK(controller_); 62 } 63 64 ResolutionChangeNotificationDelegate::~ResolutionChangeNotificationDelegate() { 65 } 66 67 void ResolutionChangeNotificationDelegate::Display() { 68 } 69 70 void ResolutionChangeNotificationDelegate::Error() { 71 } 72 73 void ResolutionChangeNotificationDelegate::Close(bool by_user) { 74 if (by_user) 75 controller_->AcceptResolutionChange(false); 76 } 77 78 void ResolutionChangeNotificationDelegate::Click() { 79 controller_->AcceptResolutionChange(true); 80 } 81 82 bool ResolutionChangeNotificationDelegate::HasClickedListener() { 83 return true; 84 } 85 86 void ResolutionChangeNotificationDelegate::ButtonClick(int button_index) { 87 // If there's the timeout, the first button is "Accept". Otherwise the 88 // button click should be "Revert". 89 if (has_timeout_ && button_index == 0) 90 controller_->AcceptResolutionChange(true); 91 else 92 controller_->RevertResolutionChange(); 93 } 94 95 } // namespace 96 97 // static 98 const int ResolutionNotificationController::kTimeoutInSec = 15; 99 100 // static 101 const char ResolutionNotificationController::kNotificationId[] = 102 "chrome://settings/display/resolution"; 103 104 struct ResolutionNotificationController::ResolutionChangeInfo { 105 ResolutionChangeInfo(int64 display_id, 106 const DisplayMode& old_resolution, 107 const DisplayMode& new_resolution, 108 const base::Closure& accept_callback); 109 ~ResolutionChangeInfo(); 110 111 // The id of the display where the resolution change happens. 112 int64 display_id; 113 114 // The resolution before the change. 115 DisplayMode old_resolution; 116 117 // The requested resolution. Note that this may be different from 118 // |current_resolution| which is the actual resolution set. 119 DisplayMode new_resolution; 120 121 // The actual resolution after the change. 122 DisplayMode current_resolution; 123 124 // The callback when accept is chosen. 125 base::Closure accept_callback; 126 127 // The remaining timeout in seconds. 0 if the change does not time out. 128 uint8 timeout_count; 129 130 // The timer to invoke OnTimerTick() every second. This cannot be 131 // OneShotTimer since the message contains text "automatically closed in xx 132 // seconds..." which has to be updated every second. 133 base::RepeatingTimer<ResolutionNotificationController> timer; 134 135 private: 136 DISALLOW_COPY_AND_ASSIGN(ResolutionChangeInfo); 137 }; 138 139 ResolutionNotificationController::ResolutionChangeInfo::ResolutionChangeInfo( 140 int64 display_id, 141 const DisplayMode& old_resolution, 142 const DisplayMode& new_resolution, 143 const base::Closure& accept_callback) 144 : display_id(display_id), 145 old_resolution(old_resolution), 146 new_resolution(new_resolution), 147 accept_callback(accept_callback), 148 timeout_count(0) { 149 DisplayManager* display_manager = Shell::GetInstance()->display_manager(); 150 if (!display_manager->HasInternalDisplay() && 151 display_manager->num_connected_displays() == 1u) { 152 timeout_count = kTimeoutInSec; 153 } 154 } 155 156 ResolutionNotificationController::ResolutionChangeInfo:: 157 ~ResolutionChangeInfo() { 158 } 159 160 ResolutionNotificationController::ResolutionNotificationController() { 161 Shell::GetInstance()->display_controller()->AddObserver(this); 162 Shell::GetScreen()->AddObserver(this); 163 } 164 165 ResolutionNotificationController::~ResolutionNotificationController() { 166 Shell::GetInstance()->display_controller()->RemoveObserver(this); 167 Shell::GetScreen()->RemoveObserver(this); 168 } 169 170 void ResolutionNotificationController::PrepareNotification( 171 int64 display_id, 172 const DisplayMode& old_resolution, 173 const DisplayMode& new_resolution, 174 const base::Closure& accept_callback) { 175 // If multiple resolution changes are invoked for the same display, 176 // the original resolution for the first resolution change has to be used 177 // instead of the specified |old_resolution|. 178 DisplayMode original_resolution; 179 if (change_info_ && change_info_->display_id == display_id) { 180 DCHECK(change_info_->new_resolution.size == old_resolution.size); 181 original_resolution = change_info_->old_resolution; 182 } 183 184 change_info_.reset(new ResolutionChangeInfo( 185 display_id, old_resolution, new_resolution, accept_callback)); 186 if (!original_resolution.size.IsEmpty()) 187 change_info_->old_resolution = original_resolution; 188 } 189 190 bool ResolutionNotificationController::DoesNotificationTimeout() { 191 return change_info_ && change_info_->timeout_count > 0; 192 } 193 194 void ResolutionNotificationController::CreateOrUpdateNotification( 195 bool enable_spoken_feedback) { 196 message_center::MessageCenter* message_center = 197 message_center::MessageCenter::Get(); 198 if (!change_info_) { 199 message_center->RemoveNotification(kNotificationId, false /* by_user */); 200 return; 201 } 202 203 base::string16 timeout_message; 204 message_center::RichNotificationData data; 205 if (change_info_->timeout_count > 0) { 206 data.buttons.push_back(message_center::ButtonInfo( 207 l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_RESOLUTION_CHANGE_ACCEPT))); 208 timeout_message = l10n_util::GetStringFUTF16( 209 IDS_ASH_DISPLAY_RESOLUTION_TIMEOUT, 210 ui::TimeFormat::Simple( 211 ui::TimeFormat::FORMAT_DURATION, ui::TimeFormat::LENGTH_LONG, 212 base::TimeDelta::FromSeconds(change_info_->timeout_count))); 213 } 214 data.buttons.push_back(message_center::ButtonInfo( 215 l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_RESOLUTION_CHANGE_REVERT))); 216 217 data.should_make_spoken_feedback_for_popup_updates = enable_spoken_feedback; 218 219 const base::string16 display_name = base::UTF8ToUTF16( 220 Shell::GetInstance()->display_manager()->GetDisplayNameForId( 221 change_info_->display_id)); 222 const base::string16 message = 223 (change_info_->new_resolution.size == 224 change_info_->current_resolution.size) ? 225 l10n_util::GetStringFUTF16( 226 IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED, 227 display_name, 228 base::UTF8ToUTF16(change_info_->new_resolution.size.ToString())) : 229 l10n_util::GetStringFUTF16( 230 IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED_TO_UNSUPPORTED, 231 display_name, 232 base::UTF8ToUTF16(change_info_->new_resolution.size.ToString()), 233 base::UTF8ToUTF16(change_info_->current_resolution.size.ToString())); 234 235 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 236 scoped_ptr<Notification> notification(new Notification( 237 message_center::NOTIFICATION_TYPE_SIMPLE, 238 kNotificationId, 239 message, 240 timeout_message, 241 bundle.GetImageNamed(IDR_AURA_NOTIFICATION_DISPLAY), 242 base::string16() /* display_source */, 243 message_center::NotifierId( 244 message_center::NotifierId::SYSTEM_COMPONENT, 245 system_notifier::kNotifierDisplayResolutionChange), 246 data, 247 new ResolutionChangeNotificationDelegate( 248 this, change_info_->timeout_count > 0))); 249 notification->SetSystemPriority(); 250 message_center->AddNotification(notification.Pass()); 251 } 252 253 void ResolutionNotificationController::OnTimerTick() { 254 if (!change_info_) 255 return; 256 257 --change_info_->timeout_count; 258 if (change_info_->timeout_count == 0) 259 RevertResolutionChange(); 260 else 261 CreateOrUpdateNotification(false); 262 } 263 264 void ResolutionNotificationController::AcceptResolutionChange( 265 bool close_notification) { 266 if (close_notification) { 267 message_center::MessageCenter::Get()->RemoveNotification( 268 kNotificationId, false /* by_user */); 269 } 270 base::Closure callback = change_info_->accept_callback; 271 change_info_.reset(); 272 callback.Run(); 273 } 274 275 void ResolutionNotificationController::RevertResolutionChange() { 276 message_center::MessageCenter::Get()->RemoveNotification( 277 kNotificationId, false /* by_user */); 278 int64 display_id = change_info_->display_id; 279 DisplayMode old_resolution = change_info_->old_resolution; 280 change_info_.reset(); 281 Shell::GetInstance()->display_manager()->SetDisplayMode( 282 display_id, old_resolution); 283 } 284 285 void ResolutionNotificationController::OnDisplayAdded( 286 const gfx::Display& new_display) { 287 } 288 289 void ResolutionNotificationController::OnDisplayRemoved( 290 const gfx::Display& old_display) { 291 if (change_info_ && change_info_->display_id == old_display.id()) 292 RevertResolutionChange(); 293 } 294 295 void ResolutionNotificationController::OnDisplayMetricsChanged( 296 const gfx::Display&, uint32_t) { 297 } 298 299 void ResolutionNotificationController::OnDisplayConfigurationChanged() { 300 if (!change_info_) 301 return; 302 303 change_info_->current_resolution = Shell::GetInstance()->display_manager()-> 304 GetActiveModeForDisplayId(change_info_->display_id); 305 CreateOrUpdateNotification(true); 306 if (g_use_timer && change_info_->timeout_count > 0) { 307 change_info_->timer.Start(FROM_HERE, 308 base::TimeDelta::FromSeconds(1), 309 this, 310 &ResolutionNotificationController::OnTimerTick); 311 } 312 } 313 314 void ResolutionNotificationController::SuppressTimerForTest() { 315 g_use_timer = false; 316 } 317 318 } // namespace ash 319