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_SUPERVISED:
    124     case user::LOGGED_IN_KIOSK_APP:
    125       ash::SystemTrayDelegate* delegate =
    126           Shell::GetInstance()->system_tray_delegate();
    127       if (delegate->ShouldShowSettings())
    128         delegate->ShowDisplaySettings();
    129   }
    130 }
    131 
    132 }  // namespace
    133 
    134 const char TrayDisplay::kNotificationId[] = "chrome://settings/display";
    135 
    136 class DisplayView : public ActionableView {
    137  public:
    138   explicit DisplayView() {
    139     SetLayoutManager(new views::BoxLayout(
    140         views::BoxLayout::kHorizontal,
    141         kTrayPopupPaddingHorizontal, 0,
    142         kTrayPopupPaddingBetweenItems));
    143 
    144     ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
    145     image_ = new FixedSizedImageView(0, kTrayPopupItemHeight);
    146     image_->SetImage(
    147         bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY).ToImageSkia());
    148     AddChildView(image_);
    149 
    150     label_ = new views::Label();
    151     label_->SetMultiLine(true);
    152     label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    153     AddChildView(label_);
    154     Update();
    155   }
    156 
    157   virtual ~DisplayView() {}
    158 
    159   void Update() {
    160     base::string16 message = GetTrayDisplayMessage(NULL);
    161     if (message.empty() && ShouldShowFirstDisplayInfo())
    162       message = GetDisplayInfoLine(GetDisplayManager()->first_display_id());
    163     SetVisible(!message.empty());
    164     label_->SetText(message);
    165     SetAccessibleName(message);
    166     Layout();
    167   }
    168 
    169   const views::Label* label() const { return label_; }
    170 
    171   // Overridden from views::View.
    172   virtual bool GetTooltipText(const gfx::Point& p,
    173                               base::string16* tooltip) const OVERRIDE {
    174     base::string16 tray_message = GetTrayDisplayMessage(NULL);
    175     base::string16 display_message = GetAllDisplayInfo();
    176     if (tray_message.empty() && display_message.empty())
    177       return false;
    178 
    179     *tooltip = tray_message + base::ASCIIToUTF16("\n") + display_message;
    180     return true;
    181   }
    182 
    183   // Returns the name of the currently connected external display.
    184   // This should not be used when the external display is used for
    185   // mirroring.
    186   static base::string16 GetExternalDisplayName() {
    187     DisplayManager* display_manager = GetDisplayManager();
    188     DCHECK(!display_manager->IsMirrored());
    189 
    190     int64 external_id = gfx::Display::kInvalidDisplayID;
    191     for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
    192       int64 id = display_manager->GetDisplayAt(i).id();
    193       if (id != gfx::Display::InternalDisplayId()) {
    194         external_id = id;
    195         break;
    196       }
    197     }
    198 
    199     if (external_id == gfx::Display::kInvalidDisplayID) {
    200       return l10n_util::GetStringUTF16(
    201           IDS_ASH_STATUS_TRAY_UNKNOWN_DISPLAY_NAME);
    202     }
    203 
    204     // The external display name may have an annotation of "(width x height)" in
    205     // case that the display is rotated or its resolution is changed.
    206     base::string16 name = GetDisplayName(external_id);
    207     const DisplayInfo& display_info =
    208         display_manager->GetDisplayInfo(external_id);
    209     if (display_info.rotation() != gfx::Display::ROTATE_0 ||
    210         display_info.configured_ui_scale() != 1.0f ||
    211         !display_info.overscan_insets_in_dip().empty()) {
    212       name = l10n_util::GetStringFUTF16(
    213           IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
    214           name, GetDisplaySize(external_id));
    215     } else if (display_info.overscan_insets_in_dip().empty() &&
    216                display_info.has_overscan()) {
    217       name = l10n_util::GetStringFUTF16(
    218           IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
    219           name, l10n_util::GetStringUTF16(
    220               IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN));
    221     }
    222 
    223     return name;
    224   }
    225 
    226   static base::string16 GetTrayDisplayMessage(
    227       base::string16* additional_message_out) {
    228     DisplayManager* display_manager = GetDisplayManager();
    229     if (display_manager->GetNumDisplays() > 1) {
    230       if (GetDisplayManager()->HasInternalDisplay()) {
    231         return l10n_util::GetStringFUTF16(
    232             IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetExternalDisplayName());
    233       }
    234       return l10n_util::GetStringUTF16(
    235           IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL);
    236     }
    237 
    238     if (display_manager->IsMirrored()) {
    239       if (GetDisplayManager()->HasInternalDisplay()) {
    240         return l10n_util::GetStringFUTF16(
    241             IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING,
    242             GetDisplayName(display_manager->mirrored_display_id()));
    243       }
    244       return l10n_util::GetStringUTF16(
    245           IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING_NO_INTERNAL);
    246     }
    247 
    248     int64 primary_id = Shell::GetScreen()->GetPrimaryDisplay().id();
    249     if (display_manager->HasInternalDisplay() &&
    250         !display_manager->IsInternalDisplayId(primary_id)) {
    251       if (additional_message_out) {
    252         *additional_message_out = l10n_util::GetStringUTF16(
    253             IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED_DESCRIPTION);
    254       }
    255       return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED);
    256     }
    257 
    258     return base::string16();
    259   }
    260 
    261  private:
    262   bool ShouldShowFirstDisplayInfo() const {
    263     const DisplayInfo& display_info = GetDisplayManager()->GetDisplayInfo(
    264         GetDisplayManager()->first_display_id());
    265     return display_info.rotation() != gfx::Display::ROTATE_0 ||
    266         display_info.configured_ui_scale() != 1.0f ||
    267         !display_info.overscan_insets_in_dip().empty() ||
    268         display_info.has_overscan();
    269   }
    270 
    271   // Overridden from ActionableView.
    272   virtual bool PerformAction(const ui::Event& event) OVERRIDE {
    273     OpenSettings();
    274     return true;
    275   }
    276 
    277   virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE {
    278     int label_max_width = bounds().width() - kTrayPopupPaddingHorizontal * 2 -
    279         kTrayPopupPaddingBetweenItems - image_->GetPreferredSize().width();
    280     label_->SizeToFit(label_max_width);
    281   }
    282 
    283   views::ImageView* image_;
    284   views::Label* label_;
    285 
    286   DISALLOW_COPY_AND_ASSIGN(DisplayView);
    287 };
    288 
    289 TrayDisplay::TrayDisplay(SystemTray* system_tray)
    290     : SystemTrayItem(system_tray),
    291       default_(NULL) {
    292   Shell::GetInstance()->display_controller()->AddObserver(this);
    293   UpdateDisplayInfo(NULL);
    294 }
    295 
    296 TrayDisplay::~TrayDisplay() {
    297   Shell::GetInstance()->display_controller()->RemoveObserver(this);
    298 }
    299 
    300 void TrayDisplay::UpdateDisplayInfo(TrayDisplay::DisplayInfoMap* old_info) {
    301   if (old_info)
    302     old_info->swap(display_info_);
    303   display_info_.clear();
    304 
    305   DisplayManager* display_manager = GetDisplayManager();
    306   for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
    307     int64 id = display_manager->GetDisplayAt(i).id();
    308     display_info_[id] = display_manager->GetDisplayInfo(id);
    309   }
    310 }
    311 
    312 bool TrayDisplay::GetDisplayMessageForNotification(
    313     const TrayDisplay::DisplayInfoMap& old_info,
    314     base::string16* message_out,
    315     base::string16* additional_message_out) {
    316   // Display is added or removed. Use the same message as the one in
    317   // the system tray.
    318   if (display_info_.size() != old_info.size()) {
    319     *message_out = DisplayView::GetTrayDisplayMessage(additional_message_out);
    320     return true;
    321   }
    322 
    323   for (DisplayInfoMap::const_iterator iter = display_info_.begin();
    324        iter != display_info_.end(); ++iter) {
    325     DisplayInfoMap::const_iterator old_iter = old_info.find(iter->first);
    326     // The display's number is same but different displays. This happens
    327     // for the transition between docked mode and mirrored display. Falls back
    328     // to GetTrayDisplayMessage().
    329     if (old_iter == old_info.end()) {
    330       *message_out = DisplayView::GetTrayDisplayMessage(additional_message_out);
    331       return true;
    332     }
    333 
    334     if (iter->second.configured_ui_scale() !=
    335         old_iter->second.configured_ui_scale()) {
    336       *message_out = l10n_util::GetStringFUTF16(
    337           IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED,
    338           GetDisplayName(iter->first),
    339           GetDisplaySize(iter->first));
    340       return true;
    341     }
    342     if (iter->second.rotation() != old_iter->second.rotation()) {
    343       int rotation_text_id = 0;
    344       switch (iter->second.rotation()) {
    345         case gfx::Display::ROTATE_0:
    346           rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION;
    347           break;
    348         case gfx::Display::ROTATE_90:
    349           rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90;
    350           break;
    351         case gfx::Display::ROTATE_180:
    352           rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180;
    353           break;
    354         case gfx::Display::ROTATE_270:
    355           rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270;
    356           break;
    357       }
    358       *message_out = l10n_util::GetStringFUTF16(
    359           IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED,
    360           GetDisplayName(iter->first),
    361           l10n_util::GetStringUTF16(rotation_text_id));
    362       return true;
    363     }
    364   }
    365 
    366   // Found nothing special
    367   return false;
    368 }
    369 
    370 void TrayDisplay::CreateOrUpdateNotification(
    371     const base::string16& message,
    372     const base::string16& additional_message) {
    373   // Always remove the notification to make sure the notification appears
    374   // as a popup in any situation.
    375   message_center::MessageCenter::Get()->RemoveNotification(
    376       kNotificationId, false /* by_user */);
    377 
    378   if (message.empty())
    379     return;
    380 
    381   // Don't display notifications for accelerometer triggered screen rotations.
    382   // See http://crbug.com/364949
    383   if (Shell::GetInstance()->maximize_mode_controller()->
    384       ignore_display_configuration_updates()) {
    385     return;
    386   }
    387 
    388   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
    389   scoped_ptr<Notification> notification(new Notification(
    390       message_center::NOTIFICATION_TYPE_SIMPLE,
    391       kNotificationId,
    392       message,
    393       additional_message,
    394       bundle.GetImageNamed(IDR_AURA_NOTIFICATION_DISPLAY),
    395       base::string16(),  // display_source
    396       message_center::NotifierId(
    397           message_center::NotifierId::SYSTEM_COMPONENT,
    398           system_notifier::kNotifierDisplay),
    399       message_center::RichNotificationData(),
    400       new message_center::HandleNotificationClickedDelegate(
    401           base::Bind(&OpenSettings))));
    402 
    403     message_center::MessageCenter::Get()->AddNotification(notification.Pass());
    404 }
    405 
    406 views::View* TrayDisplay::CreateDefaultView(user::LoginStatus status) {
    407   DCHECK(default_ == NULL);
    408   default_ = new DisplayView();
    409   return default_;
    410 }
    411 
    412 void TrayDisplay::DestroyDefaultView() {
    413   default_ = NULL;
    414 }
    415 
    416 void TrayDisplay::OnDisplayConfigurationChanged() {
    417   DisplayInfoMap old_info;
    418   UpdateDisplayInfo(&old_info);
    419 
    420   if (default_)
    421     default_->Update();
    422 
    423   if (!Shell::GetInstance()->system_tray_delegate()->
    424           ShouldShowDisplayNotification()) {
    425     return;
    426   }
    427 
    428   base::string16 message;
    429   base::string16 additional_message;
    430   if (GetDisplayMessageForNotification(old_info, &message, &additional_message))
    431     CreateOrUpdateNotification(message, additional_message);
    432 }
    433 
    434 base::string16 TrayDisplay::GetDefaultViewMessage() const {
    435   if (!default_ || !default_->visible())
    436     return base::string16();
    437 
    438   return static_cast<DisplayView*>(default_)->label()->text();
    439 }
    440 
    441 bool TrayDisplay::GetAccessibleStateForTesting(ui::AXViewState* state) {
    442   views::View* view = default_;
    443   if (view) {
    444     view->GetAccessibleState(state);
    445     return true;
    446   }
    447   return false;
    448 }
    449 
    450 }  // namespace ash
    451