Home | History | Annotate | Download | only in network
      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/network/tray_sms.h"
      6 
      7 #include "ash/shell.h"
      8 #include "ash/system/tray/fixed_sized_scroll_view.h"
      9 #include "ash/system/tray/system_tray.h"
     10 #include "ash/system/tray/system_tray_bubble.h"
     11 #include "ash/system/tray/system_tray_notifier.h"
     12 #include "ash/system/tray/tray_constants.h"
     13 #include "ash/system/tray/tray_details_view.h"
     14 #include "ash/system/tray/tray_item_more.h"
     15 #include "ash/system/tray/tray_item_view.h"
     16 #include "ash/system/tray/tray_notification_view.h"
     17 #include "base/strings/string_number_conversions.h"
     18 #include "base/strings/utf_string_conversions.h"
     19 #include "chromeos/network/network_event_log.h"
     20 #include "chromeos/network/network_handler.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/views/bubble/tray_bubble_view.h"
     26 #include "ui/views/controls/image_view.h"
     27 #include "ui/views/controls/label.h"
     28 #include "ui/views/layout/box_layout.h"
     29 #include "ui/views/layout/fill_layout.h"
     30 #include "ui/views/layout/grid_layout.h"
     31 #include "ui/views/view.h"
     32 
     33 namespace {
     34 
     35 // Min height of the list of messages in the popup.
     36 const int kMessageListMinHeight = 200;
     37 // Top/bottom padding of the text items.
     38 const int kPaddingVertical = 10;
     39 
     40 const char kSmsNumberKey[] = "number";
     41 const char kSmsTextKey[] = "text";
     42 
     43 bool GetMessageFromDictionary(const base::DictionaryValue* message,
     44                               std::string* number,
     45                               std::string* text) {
     46   if (!message->GetStringWithoutPathExpansion(kSmsNumberKey, number))
     47     return false;
     48   if (!message->GetStringWithoutPathExpansion(kSmsTextKey, text))
     49     return false;
     50   return true;
     51 }
     52 
     53 }  // namespace
     54 
     55 namespace ash {
     56 namespace internal {
     57 
     58 class TraySms::SmsDefaultView : public TrayItemMore {
     59  public:
     60   explicit SmsDefaultView(TraySms* owner)
     61       : TrayItemMore(owner, true) {
     62     SetImage(ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
     63         IDR_AURA_UBER_TRAY_SMS));
     64     Update();
     65   }
     66 
     67   virtual ~SmsDefaultView() {}
     68 
     69   void Update() {
     70     int message_count = static_cast<TraySms*>(owner())->messages().GetSize();
     71     base::string16 label = l10n_util::GetStringFUTF16(
     72         IDS_ASH_STATUS_TRAY_SMS_MESSAGES, base::IntToString16(message_count));
     73     SetLabel(label);
     74     SetAccessibleName(label);
     75   }
     76 
     77  private:
     78   DISALLOW_COPY_AND_ASSIGN(SmsDefaultView);
     79 };
     80 
     81 // An entry (row) in SmsDetailedView or NotificationView.
     82 class TraySms::SmsMessageView : public views::View,
     83                                 public views::ButtonListener {
     84  public:
     85   enum ViewType {
     86     VIEW_DETAILED,
     87     VIEW_NOTIFICATION
     88   };
     89 
     90   SmsMessageView(TraySms* owner,
     91                  ViewType view_type,
     92                  size_t index,
     93                  const std::string& number,
     94                  const std::string& message)
     95       : owner_(owner),
     96         index_(index) {
     97     number_label_ = new views::Label(
     98         l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_SMS_NUMBER,
     99                                    UTF8ToUTF16(number)));
    100     number_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    101     number_label_->SetFont(
    102         number_label_->font().DeriveFont(0, gfx::Font::BOLD));
    103 
    104     message_label_ = new views::Label(UTF8ToUTF16(message));
    105     message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    106     message_label_->SetMultiLine(true);
    107 
    108     if (view_type == VIEW_DETAILED)
    109       LayoutDetailedView();
    110     else
    111       LayoutNotificationView();
    112   }
    113 
    114   virtual ~SmsMessageView() {
    115   }
    116 
    117   // Overridden from ButtonListener.
    118   virtual void ButtonPressed(views::Button* sender,
    119                              const ui::Event& event) OVERRIDE {
    120     owner_->RemoveMessage(index_);
    121     owner_->Update(false);
    122   }
    123 
    124  private:
    125   void LayoutDetailedView() {
    126     views::ImageButton* close_button = new views::ImageButton(this);
    127     close_button->SetImage(views::CustomButton::STATE_NORMAL,
    128         ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
    129             IDR_AURA_WINDOW_CLOSE));
    130     const int msg_width = owner_->system_tray()->GetSystemBubble()->
    131         bubble_view()->GetPreferredSize().width() -
    132             (kNotificationIconWidth + kTrayPopupPaddingHorizontal * 2);
    133     message_label_->SizeToFit(msg_width);
    134 
    135     views::GridLayout* layout = new views::GridLayout(this);
    136     SetLayoutManager(layout);
    137 
    138     views::ColumnSet* columns = layout->AddColumnSet(0);
    139 
    140     // Message
    141     columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal);
    142     columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
    143                        0 /* resize percent */,
    144                        views::GridLayout::FIXED, msg_width, msg_width);
    145 
    146     // Close button
    147     columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
    148                        0, /* resize percent */
    149                        views::GridLayout::FIXED,
    150                        kNotificationIconWidth, kNotificationIconWidth);
    151 
    152 
    153     layout->AddPaddingRow(0, kPaddingVertical);
    154     layout->StartRow(0, 0);
    155     layout->AddView(number_label_);
    156     layout->AddView(close_button, 1, 2);  // 2 rows for icon
    157     layout->StartRow(0, 0);
    158     layout->AddView(message_label_);
    159 
    160     layout->AddPaddingRow(0, kPaddingVertical);
    161   }
    162 
    163   void LayoutNotificationView() {
    164     SetLayoutManager(
    165         new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
    166     AddChildView(number_label_);
    167     message_label_->SizeToFit(kTrayNotificationContentsWidth);
    168     AddChildView(message_label_);
    169   }
    170 
    171   TraySms* owner_;
    172   size_t index_;
    173   views::Label* number_label_;
    174   views::Label* message_label_;
    175 
    176   DISALLOW_COPY_AND_ASSIGN(SmsMessageView);
    177 };
    178 
    179 class TraySms::SmsDetailedView : public TrayDetailsView,
    180                                  public ViewClickListener {
    181  public:
    182   explicit SmsDetailedView(TraySms* owner)
    183       : TrayDetailsView(owner) {
    184     Init();
    185     Update();
    186   }
    187 
    188   virtual ~SmsDetailedView() {
    189   }
    190 
    191   void Init() {
    192     CreateScrollableList();
    193     CreateSpecialRow(IDS_ASH_STATUS_TRAY_SMS, this);
    194   }
    195 
    196   void Update() {
    197     UpdateMessageList();
    198     Layout();
    199     SchedulePaint();
    200   }
    201 
    202   // Overridden from views::View.
    203   virtual gfx::Size GetPreferredSize() OVERRIDE {
    204     gfx::Size preferred_size = TrayDetailsView::GetPreferredSize();
    205     if (preferred_size.height() < kMessageListMinHeight)
    206       preferred_size.set_height(kMessageListMinHeight);
    207     return preferred_size;
    208   }
    209 
    210  private:
    211   void UpdateMessageList() {
    212     const base::ListValue& messages =
    213         static_cast<TraySms*>(owner())->messages();
    214     scroll_content()->RemoveAllChildViews(true);
    215     for (size_t index = 0; index < messages.GetSize(); ++index) {
    216       const base::DictionaryValue* message = NULL;
    217       if (!messages.GetDictionary(index, &message)) {
    218         LOG(ERROR) << "SMS message not a dictionary at: " << index;
    219         continue;
    220       }
    221       std::string number, text;
    222       if (!GetMessageFromDictionary(message, &number, &text)) {
    223         LOG(ERROR) << "Error parsing SMS message";
    224         continue;
    225       }
    226       SmsMessageView* msgview = new SmsMessageView(
    227           static_cast<TraySms*>(owner()), SmsMessageView::VIEW_DETAILED, index,
    228           number, text);
    229       scroll_content()->AddChildView(msgview);
    230     }
    231     scroller()->Layout();
    232   }
    233 
    234   // Overridden from ViewClickListener.
    235   virtual void OnViewClicked(views::View* sender) OVERRIDE {
    236     if (sender == footer()->content())
    237       TransitionToDefaultView();
    238   }
    239 
    240   DISALLOW_COPY_AND_ASSIGN(SmsDetailedView);
    241 };
    242 
    243 class TraySms::SmsNotificationView : public TrayNotificationView {
    244  public:
    245   SmsNotificationView(TraySms* owner,
    246                       size_t message_index,
    247                       const std::string& number,
    248                       const std::string& text)
    249       : TrayNotificationView(owner, IDR_AURA_UBER_TRAY_SMS),
    250         message_index_(message_index) {
    251     SmsMessageView* message_view = new SmsMessageView(
    252         owner, SmsMessageView::VIEW_NOTIFICATION, message_index_, number, text);
    253     InitView(message_view);
    254   }
    255 
    256   void Update(size_t message_index,
    257               const std::string& number,
    258               const std::string& text) {
    259     SmsMessageView* message_view = new SmsMessageView(
    260         tray_sms(), SmsMessageView::VIEW_NOTIFICATION,
    261         message_index_, number, text);
    262     UpdateView(message_view);
    263   }
    264 
    265   // Overridden from TrayNotificationView:
    266   virtual void OnClose() OVERRIDE {
    267     tray_sms()->RemoveMessage(message_index_);
    268   }
    269 
    270   virtual void OnClickAction() OVERRIDE {
    271     owner()->PopupDetailedView(0, true);
    272   }
    273 
    274  private:
    275   TraySms* tray_sms() {
    276     return static_cast<TraySms*>(owner());
    277   }
    278 
    279   size_t message_index_;
    280 
    281   DISALLOW_COPY_AND_ASSIGN(SmsNotificationView);
    282 };
    283 
    284 TraySms::TraySms(SystemTray* system_tray)
    285     : SystemTrayItem(system_tray),
    286       default_(NULL),
    287       detailed_(NULL),
    288       notification_(NULL) {
    289   // TODO(armansito): SMS could be a special case for cellular that requires a
    290   // user (perhaps the owner) to be logged in. If that is the case, then an
    291   // additional check should be done before subscribing for SMS notifications.
    292   if (chromeos::NetworkHandler::IsInitialized())
    293     chromeos::NetworkHandler::Get()->network_sms_handler()->AddObserver(this);
    294 }
    295 
    296 TraySms::~TraySms() {
    297   if (chromeos::NetworkHandler::IsInitialized()) {
    298     chromeos::NetworkHandler::Get()->network_sms_handler()->RemoveObserver(
    299         this);
    300   }
    301 }
    302 
    303 views::View* TraySms::CreateDefaultView(user::LoginStatus status) {
    304   CHECK(default_ == NULL);
    305   default_ = new SmsDefaultView(this);
    306   default_->SetVisible(!messages_.empty());
    307   return default_;
    308 }
    309 
    310 views::View* TraySms::CreateDetailedView(user::LoginStatus status) {
    311   CHECK(detailed_ == NULL);
    312   HideNotificationView();
    313   if (messages_.empty())
    314     return NULL;
    315   detailed_ = new SmsDetailedView(this);
    316   return detailed_;
    317 }
    318 
    319 views::View* TraySms::CreateNotificationView(user::LoginStatus status) {
    320   CHECK(notification_ == NULL);
    321   if (detailed_)
    322     return NULL;
    323   size_t index;
    324   std::string number, text;
    325   if (GetLatestMessage(&index, &number, &text))
    326     notification_ = new SmsNotificationView(this, index, number, text);
    327   return notification_;
    328 }
    329 
    330 void TraySms::DestroyDefaultView() {
    331   default_ = NULL;
    332 }
    333 
    334 void TraySms::DestroyDetailedView() {
    335   detailed_ = NULL;
    336 }
    337 
    338 void TraySms::DestroyNotificationView() {
    339   notification_ = NULL;
    340 }
    341 
    342 void TraySms::MessageReceived(const base::DictionaryValue& message) {
    343 
    344   std::string message_text;
    345   if (!message.GetStringWithoutPathExpansion(
    346           chromeos::NetworkSmsHandler::kTextKey, &message_text)) {
    347     NET_LOG_ERROR("SMS message contains no content.", "");
    348     return;
    349   }
    350   // TODO(armansito): A message might be due to a special "Message Waiting"
    351   // state that the message is in. Once SMS handling moves to shill, such
    352   // messages should be filtered there so that this check becomes unnecessary.
    353   if (message_text.empty()) {
    354     NET_LOG_DEBUG("SMS has empty content text. Ignoring.", "");
    355     return;
    356   }
    357   std::string message_number;
    358   if (!message.GetStringWithoutPathExpansion(
    359           chromeos::NetworkSmsHandler::kNumberKey, &message_number)) {
    360     NET_LOG_DEBUG("SMS contains no number. Ignoring.", "");
    361     return;
    362   }
    363 
    364   NET_LOG_DEBUG("Received SMS from: " + message_number + " with text: " +
    365                 message_text, "");
    366 
    367   base::DictionaryValue* dict = new base::DictionaryValue();
    368   dict->SetString(kSmsNumberKey, message_number);
    369   dict->SetString(kSmsTextKey, message_text);
    370   messages_.Append(dict);
    371   Update(true);
    372 }
    373 
    374 bool TraySms::GetLatestMessage(size_t* index,
    375                                std::string* number,
    376                                std::string* text) {
    377   if (messages_.empty())
    378     return false;
    379   DictionaryValue* message;
    380   size_t message_index = messages_.GetSize() - 1;
    381   if (!messages_.GetDictionary(message_index, &message))
    382     return false;
    383   if (!GetMessageFromDictionary(message, number, text))
    384     return false;
    385   *index = message_index;
    386   return true;
    387 }
    388 
    389 void TraySms::RemoveMessage(size_t index) {
    390   if (index < messages_.GetSize())
    391     messages_.Remove(index, NULL);
    392 }
    393 
    394 void TraySms::Update(bool notify) {
    395   if (messages_.empty()) {
    396     if (default_)
    397       default_->SetVisible(false);
    398     if (detailed_)
    399       HideDetailedView();
    400     HideNotificationView();
    401   } else {
    402     if (default_) {
    403       default_->SetVisible(true);
    404       default_->Update();
    405     }
    406     if (detailed_)
    407       detailed_->Update();
    408     if (notification_) {
    409       size_t index;
    410       std::string number, text;
    411       if (GetLatestMessage(&index, &number, &text))
    412         notification_->Update(index, number, text);
    413     } else if (notify) {
    414       ShowNotificationView();
    415     }
    416   }
    417 }
    418 
    419 }  // namespace internal
    420 }  // namespace ash
    421