|  | // 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 <utility> | 
|  |  | 
|  | #include "ash/common/display/display_info.h" | 
|  | #include "ash/common/system/system_notifier.h" | 
|  | #include "ash/display/display_manager.h" | 
|  | #include "ash/shell.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/display/display.h" | 
|  | #include "ui/display/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: | 
|  | ~ResolutionChangeNotificationDelegate() override; | 
|  |  | 
|  | private: | 
|  | // message_center::NotificationDelegate overrides: | 
|  | void Close(bool by_user) override; | 
|  | void Click() override; | 
|  | bool HasClickedListener() override; | 
|  | 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::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_t display_id, | 
|  | const DisplayMode& old_resolution, | 
|  | const DisplayMode& new_resolution, | 
|  | const base::Closure& accept_callback); | 
|  | ~ResolutionChangeInfo(); | 
|  |  | 
|  | // The id of the display where the resolution change happens. | 
|  | int64_t display_id; | 
|  |  | 
|  | // The resolution before the change. | 
|  | DisplayMode old_resolution; | 
|  |  | 
|  | // The requested resolution. Note that this may be different from | 
|  | // |current_resolution| which is the actual resolution set. | 
|  | DisplayMode new_resolution; | 
|  |  | 
|  | // The actual resolution after the change. | 
|  | DisplayMode 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_t 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 timer; | 
|  |  | 
|  | private: | 
|  | DISALLOW_COPY_AND_ASSIGN(ResolutionChangeInfo); | 
|  | }; | 
|  |  | 
|  | ResolutionNotificationController::ResolutionChangeInfo::ResolutionChangeInfo( | 
|  | int64_t display_id, | 
|  | const DisplayMode& old_resolution, | 
|  | const DisplayMode& 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::Display::HasInternalDisplay() && | 
|  | display_manager->num_connected_displays() == 1u) { | 
|  | timeout_count = kTimeoutInSec; | 
|  | } | 
|  | } | 
|  |  | 
|  | ResolutionNotificationController::ResolutionChangeInfo:: | 
|  | ~ResolutionChangeInfo() {} | 
|  |  | 
|  | ResolutionNotificationController::ResolutionNotificationController() { | 
|  | Shell::GetInstance()->window_tree_host_manager()->AddObserver(this); | 
|  | display::Screen::GetScreen()->AddObserver(this); | 
|  | } | 
|  |  | 
|  | ResolutionNotificationController::~ResolutionNotificationController() { | 
|  | Shell::GetInstance()->window_tree_host_manager()->RemoveObserver(this); | 
|  | display::Screen::GetScreen()->RemoveObserver(this); | 
|  | } | 
|  |  | 
|  | void ResolutionNotificationController::PrepareNotification( | 
|  | int64_t display_id, | 
|  | const DisplayMode& old_resolution, | 
|  | const DisplayMode& new_resolution, | 
|  | const base::Closure& accept_callback) { | 
|  | DCHECK(!display::Display::IsInternalDisplayId(display_id)); | 
|  | // 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|. | 
|  | DisplayMode original_resolution; | 
|  | if (change_info_ && change_info_->display_id == display_id) { | 
|  | DCHECK(change_info_->new_resolution.size == old_resolution.size); | 
|  | original_resolution = change_info_->old_resolution; | 
|  | } | 
|  |  | 
|  | change_info_.reset(new ResolutionChangeInfo(display_id, old_resolution, | 
|  | new_resolution, accept_callback)); | 
|  | if (!original_resolution.size.IsEmpty()) | 
|  | change_info_->old_resolution = original_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.size == | 
|  | change_info_->current_resolution.size) | 
|  | ? l10n_util::GetStringFUTF16( | 
|  | IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED, display_name, | 
|  | base::UTF8ToUTF16(change_info_->new_resolution.size.ToString())) | 
|  | : l10n_util::GetStringFUTF16( | 
|  | IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED_TO_UNSUPPORTED, | 
|  | display_name, | 
|  | base::UTF8ToUTF16(change_info_->new_resolution.size.ToString()), | 
|  | base::UTF8ToUTF16( | 
|  | change_info_->current_resolution.size.ToString())); | 
|  |  | 
|  | ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); | 
|  | std::unique_ptr<Notification> notification(new Notification( | 
|  | message_center::NOTIFICATION_TYPE_SIMPLE, kNotificationId, message, | 
|  | timeout_message, bundle.GetImageNamed(IDR_AURA_NOTIFICATION_DISPLAY), | 
|  | base::string16() /* display_source */, GURL(), | 
|  | 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(std::move(notification)); | 
|  | } | 
|  |  | 
|  | 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 */); | 
|  | } | 
|  | if (!change_info_) | 
|  | return; | 
|  | base::Closure callback = change_info_->accept_callback; | 
|  | change_info_.reset(); | 
|  | callback.Run(); | 
|  | } | 
|  |  | 
|  | void ResolutionNotificationController::RevertResolutionChange() { | 
|  | message_center::MessageCenter::Get()->RemoveNotification(kNotificationId, | 
|  | false /* by_user */); | 
|  | if (!change_info_) | 
|  | return; | 
|  | int64_t display_id = change_info_->display_id; | 
|  | DisplayMode old_resolution = change_info_->old_resolution; | 
|  | change_info_.reset(); | 
|  | Shell::GetInstance()->display_manager()->SetDisplayMode(display_id, | 
|  | old_resolution); | 
|  | } | 
|  |  | 
|  | void ResolutionNotificationController::OnDisplayAdded( | 
|  | const display::Display& new_display) {} | 
|  |  | 
|  | void ResolutionNotificationController::OnDisplayRemoved( | 
|  | const display::Display& old_display) { | 
|  | if (change_info_ && change_info_->display_id == old_display.id()) | 
|  | RevertResolutionChange(); | 
|  | } | 
|  |  | 
|  | void ResolutionNotificationController::OnDisplayMetricsChanged( | 
|  | const display::Display&, | 
|  | uint32_t) {} | 
|  |  | 
|  | void ResolutionNotificationController::OnDisplayConfigurationChanged() { | 
|  | if (!change_info_) | 
|  | return; | 
|  |  | 
|  | change_info_->current_resolution = | 
|  | Shell::GetInstance()->display_manager()->GetActiveModeForDisplayId( | 
|  | change_info_->display_id); | 
|  | 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 |