Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / ash / system / chromeos / network / tray_sms.cc
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
57 class TraySms::SmsDefaultView : public TrayItemMore {
58  public:
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
66   virtual ~SmsDefaultView() {}
67
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
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
113   virtual ~SmsMessageView() {
114   }
115
116   // Overridden from ButtonListener.
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:
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
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   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
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 ash