• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetMessageFromDictionary(const base::DictionaryValue * message,std::string * number,std::string * text)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 
57 class TraySms::SmsDefaultView : public TrayItemMore {
58  public:
SmsDefaultView(TraySms * owner)59   explicit SmsDefaultView(TraySms* owner)
60       : TrayItemMore(owner, true) {
61     SetImage(ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
62         IDR_AURA_UBER_TRAY_SMS));
63     Update();
64   }
65 
~SmsDefaultView()66   virtual ~SmsDefaultView() {}
67 
Update()68   void Update() {
69     int message_count = static_cast<TraySms*>(owner())->messages().GetSize();
70     base::string16 label = l10n_util::GetStringFUTF16(
71         IDS_ASH_STATUS_TRAY_SMS_MESSAGES, base::IntToString16(message_count));
72     SetLabel(label);
73     SetAccessibleName(label);
74   }
75 
76  private:
77   DISALLOW_COPY_AND_ASSIGN(SmsDefaultView);
78 };
79 
80 // An entry (row) in SmsDetailedView or NotificationView.
81 class TraySms::SmsMessageView : public views::View,
82                                 public views::ButtonListener {
83  public:
84   enum ViewType {
85     VIEW_DETAILED,
86     VIEW_NOTIFICATION
87   };
88 
SmsMessageView(TraySms * owner,ViewType view_type,size_t index,const std::string & number,const std::string & message)89   SmsMessageView(TraySms* owner,
90                  ViewType view_type,
91                  size_t index,
92                  const std::string& number,
93                  const std::string& message)
94       : owner_(owner),
95         index_(index) {
96     number_label_ = new views::Label(
97         l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_SMS_NUMBER,
98                                    base::UTF8ToUTF16(number)),
99         ui::ResourceBundle::GetSharedInstance().GetFontList(
100             ui::ResourceBundle::BoldFont));
101     number_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
102 
103     message_label_ = new views::Label(base::UTF8ToUTF16(message));
104     message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
105     message_label_->SetMultiLine(true);
106 
107     if (view_type == VIEW_DETAILED)
108       LayoutDetailedView();
109     else
110       LayoutNotificationView();
111   }
112 
~SmsMessageView()113   virtual ~SmsMessageView() {
114   }
115 
116   // Overridden from ButtonListener.
ButtonPressed(views::Button * sender,const ui::Event & event)117   virtual void ButtonPressed(views::Button* sender,
118                              const ui::Event& event) OVERRIDE {
119     owner_->RemoveMessage(index_);
120     owner_->Update(false);
121   }
122 
123  private:
LayoutDetailedView()124   void LayoutDetailedView() {
125     views::ImageButton* close_button = new views::ImageButton(this);
126     close_button->SetImage(
127         views::CustomButton::STATE_NORMAL,
128         ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
129             IDR_AURA_UBER_TRAY_SMS_DISMISS));
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 
LayoutNotificationView()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:
SmsDetailedView(TraySms * owner)182   explicit SmsDetailedView(TraySms* owner)
183       : TrayDetailsView(owner) {
184     Init();
185     Update();
186   }
187 
~SmsDetailedView()188   virtual ~SmsDetailedView() {
189   }
190 
Init()191   void Init() {
192     CreateScrollableList();
193     CreateSpecialRow(IDS_ASH_STATUS_TRAY_SMS, this);
194   }
195 
Update()196   void Update() {
197     UpdateMessageList();
198     Layout();
199     SchedulePaint();
200   }
201 
202   // Overridden from views::View.
GetPreferredSize() const203   virtual gfx::Size GetPreferredSize() const 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:
UpdateMessageList()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.
OnViewClicked(views::View * sender)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:
SmsNotificationView(TraySms * owner,size_t message_index,const std::string & number,const std::string & text)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 
Update(size_t message_index,const std::string & number,const std::string & text)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:
OnClose()266   virtual void OnClose() OVERRIDE {
267     tray_sms()->RemoveMessage(message_index_);
268   }
269 
OnClickAction()270   virtual void OnClickAction() OVERRIDE {
271     owner()->PopupDetailedView(0, true);
272   }
273 
274  private:
tray_sms()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 
TraySms(SystemTray * system_tray)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 
~TraySms()296 TraySms::~TraySms() {
297   if (chromeos::NetworkHandler::IsInitialized()) {
298     chromeos::NetworkHandler::Get()->network_sms_handler()->RemoveObserver(
299         this);
300   }
301 }
302 
CreateDefaultView(user::LoginStatus status)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 
CreateDetailedView(user::LoginStatus status)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 
CreateNotificationView(user::LoginStatus status)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 
DestroyDefaultView()330 void TraySms::DestroyDefaultView() {
331   default_ = NULL;
332 }
333 
DestroyDetailedView()334 void TraySms::DestroyDetailedView() {
335   detailed_ = NULL;
336 }
337 
DestroyNotificationView()338 void TraySms::DestroyNotificationView() {
339   notification_ = NULL;
340 }
341 
MessageReceived(const base::DictionaryValue & message)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 
GetLatestMessage(size_t * index,std::string * number,std::string * text)374 bool TraySms::GetLatestMessage(size_t* index,
375                                std::string* number,
376                                std::string* text) {
377   if (messages_.empty())
378     return false;
379   base::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 
RemoveMessage(size_t index)389 void TraySms::RemoveMessage(size_t index) {
390   if (index < messages_.GetSize())
391     messages_.Remove(index, NULL);
392 }
393 
Update(bool notify)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 ash
420