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