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