Home | History | Annotate | Download | only in chromeos
      1 // Copyright (c) 2012 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/system/chromeos/tray_display.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 "ash/system/tray/actionable_view.h"
     12 #include "ash/system/tray/fixed_sized_image_view.h"
     13 #include "ash/system/tray/system_tray.h"
     14 #include "ash/system/tray/system_tray_delegate.h"
     15 #include "ash/system/tray/tray_constants.h"
     16 #include "ash/system/tray/tray_notification_view.h"
     17 #include "base/bind.h"
     18 #include "base/strings/string_util.h"
     19 #include "base/strings/utf_string_conversions.h"
     20 #include "grit/ash_resources.h"
     21 #include "grit/ash_strings.h"
     22 #include "ui/base/l10n/l10n_util.h"
     23 #include "ui/base/resource/resource_bundle.h"
     24 #include "ui/message_center/message_center.h"
     25 #include "ui/message_center/notification.h"
     26 #include "ui/message_center/notification_delegate.h"
     27 #include "ui/views/controls/image_view.h"
     28 #include "ui/views/controls/label.h"
     29 #include "ui/views/layout/box_layout.h"
     30 
     31 using message_center::Notification;
     32 
     33 namespace ash {
     34 namespace internal {
     35 namespace {
     36 
     37 DisplayManager* GetDisplayManager() {
     38   return Shell::GetInstance()->display_manager();
     39 }
     40 
     41 base::string16 GetDisplayName(int64 display_id) {
     42   return UTF8ToUTF16(GetDisplayManager()->GetDisplayNameForId(display_id));
     43 }
     44 
     45 base::string16 GetDisplaySize(int64 display_id) {
     46   DisplayManager* display_manager = GetDisplayManager();
     47 
     48   const gfx::Display* display = &display_manager->GetDisplayForId(display_id);
     49 
     50   // We don't show display size for mirrored display. Fallback
     51   // to empty string if this happens on release build.
     52   bool mirrored_display = display_manager->mirrored_display_id() == display_id;
     53   DCHECK(!mirrored_display);
     54   if (mirrored_display)
     55     return base::string16();
     56 
     57   DCHECK(display->is_valid());
     58   return UTF8ToUTF16(display->size().ToString());
     59 }
     60 
     61 // Returns 1-line information for the specified display, like
     62 // "InternalDisplay: 1280x750"
     63 base::string16 GetDisplayInfoLine(int64 display_id) {
     64   const DisplayInfo& display_info =
     65       GetDisplayManager()->GetDisplayInfo(display_id);
     66   if (GetDisplayManager()->mirrored_display_id() == display_id)
     67     return GetDisplayName(display_id);
     68 
     69   base::string16 size_text = GetDisplaySize(display_id);
     70   base::string16 display_data;
     71   if (display_info.has_overscan()) {
     72     display_data = l10n_util::GetStringFUTF16(
     73         IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION,
     74         size_text,
     75         l10n_util::GetStringUTF16(
     76             IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN));
     77   } else {
     78     display_data = size_text;
     79   }
     80 
     81   return l10n_util::GetStringFUTF16(
     82       IDS_ASH_STATUS_TRAY_DISPLAY_SINGLE_DISPLAY,
     83       GetDisplayName(display_id),
     84       display_data);
     85 }
     86 
     87 base::string16 GetAllDisplayInfo() {
     88   DisplayManager* display_manager = GetDisplayManager();
     89   std::vector<base::string16> lines;
     90   int64 internal_id = gfx::Display::kInvalidDisplayID;
     91   // Make sure to show the internal display first.
     92   if (display_manager->HasInternalDisplay() &&
     93       display_manager->IsInternalDisplayId(
     94           display_manager->first_display_id())) {
     95     internal_id = display_manager->first_display_id();
     96     lines.push_back(GetDisplayInfoLine(internal_id));
     97   }
     98 
     99   for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
    100     int64 id = display_manager->GetDisplayAt(i).id();
    101     if (id == internal_id)
    102       continue;
    103     lines.push_back(GetDisplayInfoLine(id));
    104   }
    105 
    106   return JoinString(lines, '\n');
    107 }
    108 
    109 void OpenSettings() {
    110   // switch is intentionally introduced without default, to cause an error when
    111   // a new type of login status is introduced.
    112   switch (Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus()) {
    113     case user::LOGGED_IN_NONE:
    114     case user::LOGGED_IN_LOCKED:
    115       return;
    116 
    117     case user::LOGGED_IN_USER:
    118     case user::LOGGED_IN_OWNER:
    119     case user::LOGGED_IN_GUEST:
    120     case user::LOGGED_IN_RETAIL_MODE:
    121     case user::LOGGED_IN_PUBLIC:
    122     case user::LOGGED_IN_LOCALLY_MANAGED:
    123     case user::LOGGED_IN_KIOSK_APP:
    124       Shell::GetInstance()->system_tray_delegate()->ShowDisplaySettings();
    125   }
    126 }
    127 
    128 }  // namespace
    129 
    130 const char TrayDisplay::kNotificationId[] = "chrome://settings/display";
    131 
    132 class DisplayView : public internal::ActionableView {
    133  public:
    134   explicit DisplayView() {
    135     SetLayoutManager(new views::BoxLayout(
    136         views::BoxLayout::kHorizontal,
    137         kTrayPopupPaddingHorizontal, 0,
    138         kTrayPopupPaddingBetweenItems));
    139 
    140     ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
    141     image_ = new internal::FixedSizedImageView(0, kTrayPopupItemHeight);
    142     image_->SetImage(
    143         bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY).ToImageSkia());
    144     AddChildView(image_);
    145 
    146     label_ = new views::Label();
    147     label_->SetMultiLine(true);
    148     label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    149     AddChildView(label_);
    150     Update();
    151   }
    152 
    153   virtual ~DisplayView() {}
    154 
    155   void Update() {
    156     base::string16 message = GetTrayDisplayMessage(NULL);
    157     if (message.empty() && ShouldShowFirstDisplayInfo())
    158       message = GetDisplayInfoLine(GetDisplayManager()->first_display_id());
    159     SetVisible(!message.empty());
    160     label_->SetText(message);
    161     Layout();
    162   }
    163 
    164   const views::Label* label() const { return label_; }
    165 
    166   // Overridden from views::View.
    167   virtual bool GetTooltipText(const gfx::Point& p,
    168                               base::string16* tooltip) const OVERRIDE {
    169     base::string16 tray_message = GetTrayDisplayMessage(NULL);
    170     base::string16 display_message = GetAllDisplayInfo();
    171     if (tray_message.empty() && display_message.empty())
    172       return false;
    173 
    174     *tooltip = tray_message + ASCIIToUTF16("\n") + display_message;
    175     return true;
    176   }
    177 
    178   // Returns the name of the currently connected external display.
    179   // This should not be used when the external display is used for
    180   // mirroring.
    181   static base::string16 GetExternalDisplayName() {
    182     DisplayManager* display_manager = GetDisplayManager();
    183     DCHECK(!display_manager->IsMirrored());
    184 
    185     int64 external_id = gfx::Display::kInvalidDisplayID;
    186     for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
    187       int64 id = display_manager->GetDisplayAt(i).id();
    188       if (id != gfx::Display::InternalDisplayId()) {
    189         external_id = id;
    190         break;
    191       }
    192     }
    193 
    194     if (external_id == gfx::Display::kInvalidDisplayID) {
    195       return l10n_util::GetStringUTF16(
    196           IDS_ASH_STATUS_TRAY_UNKNOWN_DISPLAY_NAME);
    197     }
    198 
    199     // The external display name may have an annotation of "(width x height)" in
    200     // case that the display is rotated or its resolution is changed.
    201     base::string16 name = GetDisplayName(external_id);
    202     const DisplayInfo& display_info =
    203         display_manager->GetDisplayInfo(external_id);
    204     if (display_info.rotation() != gfx::Display::ROTATE_0 ||
    205         display_info.configured_ui_scale() != 1.0f ||
    206         !display_info.overscan_insets_in_dip().empty()) {
    207       name = l10n_util::GetStringFUTF16(
    208           IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
    209           name, GetDisplaySize(external_id));
    210     } else if (display_info.overscan_insets_in_dip().empty() &&
    211                display_info.has_overscan()) {
    212       name = l10n_util::GetStringFUTF16(
    213           IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
    214           name, l10n_util::GetStringUTF16(
    215               IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN));
    216     }
    217 
    218     return name;
    219   }
    220 
    221   static base::string16 GetTrayDisplayMessage(
    222       base::string16* additional_message_out) {
    223     DisplayManager* display_manager = GetDisplayManager();
    224     if (display_manager->GetNumDisplays() > 1) {
    225       if (GetDisplayManager()->HasInternalDisplay()) {
    226         return l10n_util::GetStringFUTF16(
    227             IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetExternalDisplayName());
    228       }
    229       return l10n_util::GetStringUTF16(
    230           IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL);
    231     }
    232 
    233     if (display_manager->IsMirrored()) {
    234       if (GetDisplayManager()->HasInternalDisplay()) {
    235         return l10n_util::GetStringFUTF16(
    236             IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING,
    237             GetDisplayName(display_manager->mirrored_display_id()));
    238       }
    239       return l10n_util::GetStringUTF16(
    240           IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING_NO_INTERNAL);
    241     }
    242 
    243     int64 primary_id = Shell::GetScreen()->GetPrimaryDisplay().id();
    244     if (display_manager->HasInternalDisplay() &&
    245         !display_manager->IsInternalDisplayId(primary_id)) {
    246       if (additional_message_out) {
    247         *additional_message_out = l10n_util::GetStringUTF16(
    248             IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED_DESCRIPTION);
    249       }
    250       return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED);
    251     }
    252 
    253     return base::string16();
    254   }
    255 
    256  private:
    257   bool ShouldShowFirstDisplayInfo() const {
    258     const DisplayInfo& display_info = GetDisplayManager()->GetDisplayInfo(
    259         GetDisplayManager()->first_display_id());
    260     return display_info.rotation() != gfx::Display::ROTATE_0 ||
    261         display_info.configured_ui_scale() != 1.0f ||
    262         !display_info.overscan_insets_in_dip().empty() ||
    263         display_info.has_overscan();
    264   }
    265 
    266   // Overridden from ActionableView.
    267   virtual bool PerformAction(const ui::Event& event) OVERRIDE {
    268     OpenSettings();
    269     return true;
    270   }
    271 
    272   virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE {
    273     int label_max_width = bounds().width() - kTrayPopupPaddingHorizontal * 2 -
    274         kTrayPopupPaddingBetweenItems - image_->GetPreferredSize().width();
    275     label_->SizeToFit(label_max_width);
    276   }
    277 
    278   views::ImageView* image_;
    279   views::Label* label_;
    280 
    281   DISALLOW_COPY_AND_ASSIGN(DisplayView);
    282 };
    283 
    284 TrayDisplay::TrayDisplay(SystemTray* system_tray)
    285     : SystemTrayItem(system_tray),
    286       default_(NULL) {
    287   Shell::GetInstance()->display_controller()->AddObserver(this);
    288   UpdateDisplayInfo(NULL);
    289 }
    290 
    291 TrayDisplay::~TrayDisplay() {
    292   Shell::GetInstance()->display_controller()->RemoveObserver(this);
    293 }
    294 
    295 void TrayDisplay::UpdateDisplayInfo(TrayDisplay::DisplayInfoMap* old_info) {
    296   if (old_info)
    297     old_info->swap(display_info_);
    298   display_info_.clear();
    299 
    300   DisplayManager* display_manager = GetDisplayManager();
    301   for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
    302     int64 id = display_manager->GetDisplayAt(i).id();
    303     display_info_[id] = display_manager->GetDisplayInfo(id);
    304   }
    305 }
    306 
    307 bool TrayDisplay::GetDisplayMessageForNotification(
    308     const TrayDisplay::DisplayInfoMap& old_info,
    309     base::string16* message_out,
    310     base::string16* additional_message_out) {
    311   // Display is added or removed. Use the same message as the one in
    312   // the system tray.
    313   if (display_info_.size() != old_info.size()) {
    314     *message_out = DisplayView::GetTrayDisplayMessage(additional_message_out);
    315     return true;
    316   }
    317 
    318   for (DisplayInfoMap::const_iterator iter = display_info_.begin();
    319        iter != display_info_.end(); ++iter) {
    320     DisplayInfoMap::const_iterator old_iter = old_info.find(iter->first);
    321     // The display's number is same but different displays. This happens
    322     // for the transition between docked mode and mirrored display. Falls back
    323     // to GetTrayDisplayMessage().
    324     if (old_iter == old_info.end()) {
    325       *message_out = DisplayView::GetTrayDisplayMessage(additional_message_out);
    326       return true;
    327     }
    328 
    329     if (iter->second.configured_ui_scale() !=
    330         old_iter->second.configured_ui_scale()) {
    331       *message_out = l10n_util::GetStringFUTF16(
    332           IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED,
    333           GetDisplayName(iter->first),
    334           GetDisplaySize(iter->first));
    335       return true;
    336     }
    337     if (iter->second.rotation() != old_iter->second.rotation()) {
    338       int rotation_text_id = 0;
    339       switch (iter->second.rotation()) {
    340         case gfx::Display::ROTATE_0:
    341           rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION;
    342           break;
    343         case gfx::Display::ROTATE_90:
    344           rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90;
    345           break;
    346         case gfx::Display::ROTATE_180:
    347           rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180;
    348           break;
    349         case gfx::Display::ROTATE_270:
    350           rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270;
    351           break;
    352       }
    353       *message_out = l10n_util::GetStringFUTF16(
    354           IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED,
    355           GetDisplayName(iter->first),
    356           l10n_util::GetStringUTF16(rotation_text_id));
    357       return true;
    358     }
    359   }
    360 
    361   // Found nothing special
    362   return false;
    363 }
    364 
    365 void TrayDisplay::CreateOrUpdateNotification(
    366     const base::string16& message,
    367     const base::string16& additional_message) {
    368   // Always remove the notification to make sure the notification appears
    369   // as a popup in any situation.
    370   message_center::MessageCenter::Get()->RemoveNotification(
    371       kNotificationId, false /* by_user */);
    372 
    373   if (message.empty())
    374     return;
    375 
    376   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
    377   scoped_ptr<Notification> notification(new Notification(
    378       message_center::NOTIFICATION_TYPE_SIMPLE,
    379       kNotificationId,
    380       message,
    381       additional_message,
    382       bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY),
    383       base::string16(),  // display_source
    384       message_center::NotifierId(
    385           message_center::NotifierId::SYSTEM_COMPONENT,
    386           system_notifier::kNotifierDisplay),
    387       message_center::RichNotificationData(),
    388       new message_center::HandleNotificationClickedDelegate(
    389           base::Bind(&OpenSettings))));
    390   message_center::MessageCenter::Get()->AddNotification(notification.Pass());
    391 }
    392 
    393 views::View* TrayDisplay::CreateDefaultView(user::LoginStatus status) {
    394   DCHECK(default_ == NULL);
    395   default_ = new DisplayView();
    396   return default_;
    397 }
    398 
    399 void TrayDisplay::DestroyDefaultView() {
    400   default_ = NULL;
    401 }
    402 
    403 void TrayDisplay::OnDisplayConfigurationChanged() {
    404   DisplayInfoMap old_info;
    405   UpdateDisplayInfo(&old_info);
    406 
    407   if (default_)
    408     default_->Update();
    409 
    410   if (!Shell::GetInstance()->system_tray_delegate()->
    411           ShouldShowDisplayNotification()) {
    412     return;
    413   }
    414 
    415   base::string16 message;
    416   base::string16 additional_message;
    417   if (GetDisplayMessageForNotification(old_info, &message, &additional_message))
    418     CreateOrUpdateNotification(message, additional_message);
    419 }
    420 
    421 base::string16 TrayDisplay::GetDefaultViewMessage() const {
    422   if (!default_ || !default_->visible())
    423     return base::string16();
    424 
    425   return static_cast<DisplayView*>(default_)->label()->text();
    426 }
    427 
    428 }  // namespace internal
    429 }  // namespace ash
    430