// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ash/display/resolution_notification_controller.h" #include "ash/display/display_controller.h" #include "ash/display/display_manager.h" #include "ash/shell.h" #include "ash/system/system_notifier.h" #include "base/strings/utf_string_conversions.h" #include "grit/ash_resources.h" #include "grit/ash_strings.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/time_format.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/display.h" #include "ui/gfx/screen.h" #include "ui/message_center/message_center.h" #include "ui/message_center/notification.h" #include "ui/message_center/notification_delegate.h" using message_center::Notification; namespace ash { namespace { bool g_use_timer = true; class ResolutionChangeNotificationDelegate : public message_center::NotificationDelegate { public: ResolutionChangeNotificationDelegate( ResolutionNotificationController* controller, bool has_timeout); protected: virtual ~ResolutionChangeNotificationDelegate(); private: // message_center::NotificationDelegate overrides: virtual void Display() OVERRIDE; virtual void Error() OVERRIDE; virtual void Close(bool by_user) OVERRIDE; virtual void Click() OVERRIDE; virtual bool HasClickedListener() OVERRIDE; virtual void ButtonClick(int button_index) OVERRIDE; ResolutionNotificationController* controller_; bool has_timeout_; DISALLOW_COPY_AND_ASSIGN(ResolutionChangeNotificationDelegate); }; ResolutionChangeNotificationDelegate::ResolutionChangeNotificationDelegate( ResolutionNotificationController* controller, bool has_timeout) : controller_(controller), has_timeout_(has_timeout) { DCHECK(controller_); } ResolutionChangeNotificationDelegate::~ResolutionChangeNotificationDelegate() { } void ResolutionChangeNotificationDelegate::Display() { } void ResolutionChangeNotificationDelegate::Error() { } void ResolutionChangeNotificationDelegate::Close(bool by_user) { if (by_user) controller_->AcceptResolutionChange(false); } void ResolutionChangeNotificationDelegate::Click() { controller_->AcceptResolutionChange(true); } bool ResolutionChangeNotificationDelegate::HasClickedListener() { return true; } void ResolutionChangeNotificationDelegate::ButtonClick(int button_index) { // If there's the timeout, the first button is "Accept". Otherwise the // button click should be "Revert". if (has_timeout_ && button_index == 0) controller_->AcceptResolutionChange(true); else controller_->RevertResolutionChange(); } } // namespace // static const int ResolutionNotificationController::kTimeoutInSec = 15; // static const char ResolutionNotificationController::kNotificationId[] = "chrome://settings/display/resolution"; struct ResolutionNotificationController::ResolutionChangeInfo { ResolutionChangeInfo(int64 display_id, const gfx::Size& old_resolution, const gfx::Size& new_resolution, const base::Closure& accept_callback); ~ResolutionChangeInfo(); // The id of the display where the resolution change happens. int64 display_id; // The resolution before the change. gfx::Size old_resolution; // The requested resolution. Note that this may be different from // |current_resolution| which is the actual resolution set. gfx::Size new_resolution; // The actual resolution after the change. gfx::Size current_resolution; // The callback when accept is chosen. base::Closure accept_callback; // The remaining timeout in seconds. 0 if the change does not time out. uint8 timeout_count; // The timer to invoke OnTimerTick() every second. This cannot be // OneShotTimer since the message contains text "automatically closed in xx // seconds..." which has to be updated every second. base::RepeatingTimer<ResolutionNotificationController> timer; private: DISALLOW_COPY_AND_ASSIGN(ResolutionChangeInfo); }; ResolutionNotificationController::ResolutionChangeInfo::ResolutionChangeInfo( int64 display_id, const gfx::Size& old_resolution, const gfx::Size& new_resolution, const base::Closure& accept_callback) : display_id(display_id), old_resolution(old_resolution), new_resolution(new_resolution), accept_callback(accept_callback), timeout_count(0) { DisplayManager* display_manager = Shell::GetInstance()->display_manager(); if (!display_manager->HasInternalDisplay() && display_manager->num_connected_displays() == 1u) { timeout_count = kTimeoutInSec; } } ResolutionNotificationController::ResolutionChangeInfo:: ~ResolutionChangeInfo() { } ResolutionNotificationController::ResolutionNotificationController() { Shell::GetInstance()->display_controller()->AddObserver(this); Shell::GetScreen()->AddObserver(this); } ResolutionNotificationController::~ResolutionNotificationController() { Shell::GetInstance()->display_controller()->RemoveObserver(this); Shell::GetScreen()->RemoveObserver(this); } void ResolutionNotificationController::SetDisplayResolutionAndNotify( int64 display_id, const gfx::Size& old_resolution, const gfx::Size& new_resolution, const base::Closure& accept_callback) { // If multiple resolution changes are invoked for the same display, // the original resolution for the first resolution change has to be used // instead of the specified |old_resolution|. gfx::Size original_resolution; if (change_info_ && change_info_->display_id == display_id) { DCHECK(change_info_->new_resolution == old_resolution); original_resolution = change_info_->old_resolution; } change_info_.reset(new ResolutionChangeInfo( display_id, old_resolution, new_resolution, accept_callback)); if (!original_resolution.IsEmpty()) change_info_->old_resolution = original_resolution; // SetDisplayResolution() causes OnConfigurationChanged() and the notification // will be shown at that point. Shell::GetInstance()->display_manager()->SetDisplayResolution( display_id, new_resolution); } bool ResolutionNotificationController::DoesNotificationTimeout() { return change_info_ && change_info_->timeout_count > 0; } void ResolutionNotificationController::CreateOrUpdateNotification( bool enable_spoken_feedback) { message_center::MessageCenter* message_center = message_center::MessageCenter::Get(); if (!change_info_) { message_center->RemoveNotification(kNotificationId, false /* by_user */); return; } base::string16 timeout_message; message_center::RichNotificationData data; if (change_info_->timeout_count > 0) { data.buttons.push_back(message_center::ButtonInfo( l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_RESOLUTION_CHANGE_ACCEPT))); timeout_message = l10n_util::GetStringFUTF16( IDS_ASH_DISPLAY_RESOLUTION_TIMEOUT, ui::TimeFormat::Simple( ui::TimeFormat::FORMAT_DURATION, ui::TimeFormat::LENGTH_LONG, base::TimeDelta::FromSeconds(change_info_->timeout_count))); } data.buttons.push_back(message_center::ButtonInfo( l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_RESOLUTION_CHANGE_REVERT))); data.should_make_spoken_feedback_for_popup_updates = enable_spoken_feedback; const base::string16 display_name = base::UTF8ToUTF16( Shell::GetInstance()->display_manager()->GetDisplayNameForId( change_info_->display_id)); const base::string16 message = (change_info_->new_resolution == change_info_->current_resolution) ? l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED, display_name, base::UTF8ToUTF16(change_info_->new_resolution.ToString())) : l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED_TO_UNSUPPORTED, display_name, base::UTF8ToUTF16(change_info_->new_resolution.ToString()), base::UTF8ToUTF16(change_info_->current_resolution.ToString())); ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); scoped_ptr<Notification> notification(new Notification( message_center::NOTIFICATION_TYPE_SIMPLE, kNotificationId, message, timeout_message, bundle.GetImageNamed(IDR_AURA_NOTIFICATION_DISPLAY), base::string16() /* display_source */, message_center::NotifierId( message_center::NotifierId::SYSTEM_COMPONENT, system_notifier::kNotifierDisplayResolutionChange), data, new ResolutionChangeNotificationDelegate( this, change_info_->timeout_count > 0))); notification->SetSystemPriority(); message_center->AddNotification(notification.Pass()); } void ResolutionNotificationController::OnTimerTick() { if (!change_info_) return; --change_info_->timeout_count; if (change_info_->timeout_count == 0) RevertResolutionChange(); else CreateOrUpdateNotification(false); } void ResolutionNotificationController::AcceptResolutionChange( bool close_notification) { if (close_notification) { message_center::MessageCenter::Get()->RemoveNotification( kNotificationId, false /* by_user */); } base::Closure callback = change_info_->accept_callback; change_info_.reset(); callback.Run(); } void ResolutionNotificationController::RevertResolutionChange() { message_center::MessageCenter::Get()->RemoveNotification( kNotificationId, false /* by_user */); int64 display_id = change_info_->display_id; gfx::Size old_resolution = change_info_->old_resolution; change_info_.reset(); Shell::GetInstance()->display_manager()->SetDisplayResolution( display_id, old_resolution); } void ResolutionNotificationController::OnDisplayAdded( const gfx::Display& new_display) { } void ResolutionNotificationController::OnDisplayRemoved( const gfx::Display& old_display) { if (change_info_ && change_info_->display_id == old_display.id()) RevertResolutionChange(); } void ResolutionNotificationController::OnDisplayMetricsChanged( const gfx::Display&, uint32_t) { } void ResolutionNotificationController::OnDisplayConfigurationChanged() { if (!change_info_) return; const DisplayInfo& info = Shell::GetInstance()->display_manager()-> GetDisplayInfo(change_info_->display_id); change_info_->current_resolution = info.bounds_in_native().size(); CreateOrUpdateNotification(true); if (g_use_timer && change_info_->timeout_count > 0) { change_info_->timer.Start(FROM_HERE, base::TimeDelta::FromSeconds(1), this, &ResolutionNotificationController::OnTimerTick); } } void ResolutionNotificationController::SuppressTimerForTest() { g_use_timer = false; } } // namespace ash