Home | History | Annotate | Download | only in display
      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 "ash/system/system_notifier.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "grit/ash_resources.h"
     13 #include "grit/ash_strings.h"
     14 #include "ui/base/l10n/l10n_util.h"
     15 #include "ui/base/l10n/time_format.h"
     16 #include "ui/base/resource/resource_bundle.h"
     17 #include "ui/gfx/display.h"
     18 #include "ui/gfx/screen.h"
     19 #include "ui/message_center/message_center.h"
     20 #include "ui/message_center/notification.h"
     21 #include "ui/message_center/notification_delegate.h"
     22 
     23 using message_center::Notification;
     24 
     25 namespace ash {
     26 namespace internal {
     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 gfx::Size& old_resolution,
    107                        const gfx::Size& 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   gfx::Size old_resolution;
    116 
    117   // The new resolution after the change.
    118   gfx::Size new_resolution;
    119 
    120   // The callback when accept is chosen.
    121   base::Closure accept_callback;
    122 
    123   // The remaining timeout in seconds. 0 if the change does not time out.
    124   uint8 timeout_count;
    125 
    126   // The timer to invoke OnTimerTick() every second. This cannot be
    127   // OneShotTimer since the message contains text "automatically closed in xx
    128   // seconds..." which has to be updated every second.
    129   base::RepeatingTimer<ResolutionNotificationController> timer;
    130 
    131  private:
    132   DISALLOW_COPY_AND_ASSIGN(ResolutionChangeInfo);
    133 };
    134 
    135 ResolutionNotificationController::ResolutionChangeInfo::ResolutionChangeInfo(
    136     int64 display_id,
    137     const gfx::Size& old_resolution,
    138     const gfx::Size& new_resolution,
    139     const base::Closure& accept_callback)
    140     : display_id(display_id),
    141       old_resolution(old_resolution),
    142       new_resolution(new_resolution),
    143       accept_callback(accept_callback),
    144       timeout_count(0) {
    145   DisplayManager* display_manager = Shell::GetInstance()->display_manager();
    146   if (!display_manager->HasInternalDisplay() &&
    147       display_manager->num_connected_displays() == 1u) {
    148     timeout_count = kTimeoutInSec;
    149   }
    150 }
    151 
    152 ResolutionNotificationController::ResolutionChangeInfo::
    153     ~ResolutionChangeInfo() {
    154 }
    155 
    156 ResolutionNotificationController::ResolutionNotificationController() {
    157   Shell::GetInstance()->display_controller()->AddObserver(this);
    158   Shell::GetScreen()->AddObserver(this);
    159 }
    160 
    161 ResolutionNotificationController::~ResolutionNotificationController() {
    162   Shell::GetInstance()->display_controller()->RemoveObserver(this);
    163   Shell::GetScreen()->RemoveObserver(this);
    164 }
    165 
    166 void ResolutionNotificationController::SetDisplayResolutionAndNotify(
    167     int64 display_id,
    168     const gfx::Size& old_resolution,
    169     const gfx::Size& new_resolution,
    170     const base::Closure& accept_callback) {
    171   // If multiple resolution changes are invoked for the same display,
    172   // the original resolution for the first resolution change has to be used
    173   // instead of the specified |old_resolution|.
    174   gfx::Size original_resolution;
    175   if (change_info_ && change_info_->display_id == display_id) {
    176     DCHECK(change_info_->new_resolution == old_resolution);
    177     original_resolution = change_info_->old_resolution;
    178   }
    179 
    180   change_info_.reset(new ResolutionChangeInfo(
    181       display_id, old_resolution, new_resolution, accept_callback));
    182   if (!original_resolution.IsEmpty())
    183     change_info_->old_resolution = original_resolution;
    184 
    185   // SetDisplayResolution() causes OnConfigurationChanged() and the notification
    186   // will be shown at that point.
    187   Shell::GetInstance()->display_manager()->SetDisplayResolution(
    188       display_id, new_resolution);
    189 }
    190 
    191 bool ResolutionNotificationController::DoesNotificationTimeout() {
    192   return change_info_ && change_info_->timeout_count > 0;
    193 }
    194 
    195 void ResolutionNotificationController::CreateOrUpdateNotification(
    196     bool enable_spoken_feedback) {
    197   message_center::MessageCenter* message_center =
    198       message_center::MessageCenter::Get();
    199   if (!change_info_) {
    200     message_center->RemoveNotification(kNotificationId, false /* by_user */);
    201     return;
    202   }
    203 
    204   base::string16 timeout_message;
    205   message_center::RichNotificationData data;
    206   if (change_info_->timeout_count > 0) {
    207     data.buttons.push_back(message_center::ButtonInfo(
    208         l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_RESOLUTION_CHANGE_ACCEPT)));
    209     timeout_message = l10n_util::GetStringFUTF16(
    210         IDS_ASH_DISPLAY_RESOLUTION_TIMEOUT,
    211         ui::TimeFormat::TimeDurationLong(
    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   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
    220   scoped_ptr<Notification> notification(new Notification(
    221       message_center::NOTIFICATION_TYPE_SIMPLE,
    222       kNotificationId,
    223       l10n_util::GetStringFUTF16(
    224           IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED,
    225           UTF8ToUTF16(Shell::GetInstance()->display_manager()->
    226               GetDisplayNameForId(change_info_->display_id)),
    227           UTF8ToUTF16(change_info_->new_resolution.ToString())),
    228       timeout_message,
    229       bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY),
    230       base::string16() /* display_source */,
    231       message_center::NotifierId(
    232           message_center::NotifierId::SYSTEM_COMPONENT,
    233           system_notifier::kNotifierDisplayResolutionChange),
    234       data,
    235       new ResolutionChangeNotificationDelegate(
    236           this, change_info_->timeout_count > 0)));
    237   notification->SetSystemPriority();
    238   message_center->AddNotification(notification.Pass());
    239 }
    240 
    241 void ResolutionNotificationController::OnTimerTick() {
    242   if (!change_info_)
    243     return;
    244 
    245   --change_info_->timeout_count;
    246   if (change_info_->timeout_count == 0)
    247     RevertResolutionChange();
    248   else
    249     CreateOrUpdateNotification(false);
    250 }
    251 
    252 void ResolutionNotificationController::AcceptResolutionChange(
    253     bool close_notification) {
    254   if (close_notification) {
    255     message_center::MessageCenter::Get()->RemoveNotification(
    256         kNotificationId, false /* by_user */);
    257   }
    258   base::Closure callback = change_info_->accept_callback;
    259   change_info_.reset();
    260   callback.Run();
    261 }
    262 
    263 void ResolutionNotificationController::RevertResolutionChange() {
    264   message_center::MessageCenter::Get()->RemoveNotification(
    265       kNotificationId, false /* by_user */);
    266   int64 display_id = change_info_->display_id;
    267   gfx::Size old_resolution = change_info_->old_resolution;
    268   change_info_.reset();
    269   Shell::GetInstance()->display_manager()->SetDisplayResolution(
    270       display_id, old_resolution);
    271 }
    272 
    273 void ResolutionNotificationController::OnDisplayBoundsChanged(
    274     const gfx::Display& display) {
    275 }
    276 
    277 void ResolutionNotificationController::OnDisplayAdded(
    278     const gfx::Display& new_display) {
    279 }
    280 
    281 void ResolutionNotificationController::OnDisplayRemoved(
    282     const gfx::Display& old_display) {
    283   if (change_info_ && change_info_->display_id == old_display.id())
    284     RevertResolutionChange();
    285 }
    286 
    287 void ResolutionNotificationController::OnDisplayConfigurationChanged() {
    288   if (!change_info_)
    289     return;
    290 
    291   CreateOrUpdateNotification(true);
    292   if (g_use_timer && change_info_->timeout_count > 0) {
    293     change_info_->timer.Start(FROM_HERE,
    294                               base::TimeDelta::FromSeconds(1),
    295                               this,
    296                               &ResolutionNotificationController::OnTimerTick);
    297   }
    298 }
    299 
    300 void ResolutionNotificationController::SuppressTimerForTest() {
    301   g_use_timer = false;
    302 }
    303 
    304 }  // namespace internal
    305 }  // namespace ash
    306