Upstream version 5.34.104.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 namespace internal {
57
58 class TraySms::SmsDefaultView : public TrayItemMore {
59  public:
60   explicit SmsDefaultView(TraySms* owner)
61       : TrayItemMore(owner, true) {
62     SetImage(ui::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                                    base::UTF8ToUTF16(number)),
100         ui::ResourceBundle::GetSharedInstance().GetFontList(
101             ui::ResourceBundle::BoldFont));
102     number_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
103
104     message_label_ = new views::Label(base::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(
128         views::CustomButton::STATE_NORMAL,
129         ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
130             IDR_AURA_WINDOW_CLOSE));
131     const int msg_width = owner_->system_tray()->GetSystemBubble()->
132         bubble_view()->GetPreferredSize().width() -
133             (kNotificationIconWidth + kTrayPopupPaddingHorizontal * 2);
134     message_label_->SizeToFit(msg_width);
135
136     views::GridLayout* layout = new views::GridLayout(this);
137     SetLayoutManager(layout);
138
139     views::ColumnSet* columns = layout->AddColumnSet(0);
140
141     // Message
142     columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal);
143     columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
144                        0 /* resize percent */,
145                        views::GridLayout::FIXED, msg_width, msg_width);
146
147     // Close button
148     columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
149                        0, /* resize percent */
150                        views::GridLayout::FIXED,
151                        kNotificationIconWidth, kNotificationIconWidth);
152
153
154     layout->AddPaddingRow(0, kPaddingVertical);
155     layout->StartRow(0, 0);
156     layout->AddView(number_label_);
157     layout->AddView(close_button, 1, 2);  // 2 rows for icon
158     layout->StartRow(0, 0);
159     layout->AddView(message_label_);
160
161     layout->AddPaddingRow(0, kPaddingVertical);
162   }
163
164   void LayoutNotificationView() {
165     SetLayoutManager(
166         new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
167     AddChildView(number_label_);
168     message_label_->SizeToFit(kTrayNotificationContentsWidth);
169     AddChildView(message_label_);
170   }
171
172   TraySms* owner_;
173   size_t index_;
174   views::Label* number_label_;
175   views::Label* message_label_;
176
177   DISALLOW_COPY_AND_ASSIGN(SmsMessageView);
178 };
179
180 class TraySms::SmsDetailedView : public TrayDetailsView,
181                                  public ViewClickListener {
182  public:
183   explicit SmsDetailedView(TraySms* owner)
184       : TrayDetailsView(owner) {
185     Init();
186     Update();
187   }
188
189   virtual ~SmsDetailedView() {
190   }
191
192   void Init() {
193     CreateScrollableList();
194     CreateSpecialRow(IDS_ASH_STATUS_TRAY_SMS, this);
195   }
196
197   void Update() {
198     UpdateMessageList();
199     Layout();
200     SchedulePaint();
201   }
202
203   // Overridden from views::View.
204   virtual gfx::Size GetPreferredSize() OVERRIDE {
205     gfx::Size preferred_size = TrayDetailsView::GetPreferredSize();
206     if (preferred_size.height() < kMessageListMinHeight)
207       preferred_size.set_height(kMessageListMinHeight);
208     return preferred_size;
209   }
210
211  private:
212   void UpdateMessageList() {
213     const base::ListValue& messages =
214         static_cast<TraySms*>(owner())->messages();
215     scroll_content()->RemoveAllChildViews(true);
216     for (size_t index = 0; index < messages.GetSize(); ++index) {
217       const base::DictionaryValue* message = NULL;
218       if (!messages.GetDictionary(index, &message)) {
219         LOG(ERROR) << "SMS message not a dictionary at: " << index;
220         continue;
221       }
222       std::string number, text;
223       if (!GetMessageFromDictionary(message, &number, &text)) {
224         LOG(ERROR) << "Error parsing SMS message";
225         continue;
226       }
227       SmsMessageView* msgview = new SmsMessageView(
228           static_cast<TraySms*>(owner()), SmsMessageView::VIEW_DETAILED, index,
229           number, text);
230       scroll_content()->AddChildView(msgview);
231     }
232     scroller()->Layout();
233   }
234
235   // Overridden from ViewClickListener.
236   virtual void OnViewClicked(views::View* sender) OVERRIDE {
237     if (sender == footer()->content())
238       TransitionToDefaultView();
239   }
240
241   DISALLOW_COPY_AND_ASSIGN(SmsDetailedView);
242 };
243
244 class TraySms::SmsNotificationView : public TrayNotificationView {
245  public:
246   SmsNotificationView(TraySms* owner,
247                       size_t message_index,
248                       const std::string& number,
249                       const std::string& text)
250       : TrayNotificationView(owner, IDR_AURA_UBER_TRAY_SMS),
251         message_index_(message_index) {
252     SmsMessageView* message_view = new SmsMessageView(
253         owner, SmsMessageView::VIEW_NOTIFICATION, message_index_, number, text);
254     InitView(message_view);
255   }
256
257   void Update(size_t message_index,
258               const std::string& number,
259               const std::string& text) {
260     SmsMessageView* message_view = new SmsMessageView(
261         tray_sms(), SmsMessageView::VIEW_NOTIFICATION,
262         message_index_, number, text);
263     UpdateView(message_view);
264   }
265
266   // Overridden from TrayNotificationView:
267   virtual void OnClose() OVERRIDE {
268     tray_sms()->RemoveMessage(message_index_);
269   }
270
271   virtual void OnClickAction() OVERRIDE {
272     owner()->PopupDetailedView(0, true);
273   }
274
275  private:
276   TraySms* tray_sms() {
277     return static_cast<TraySms*>(owner());
278   }
279
280   size_t message_index_;
281
282   DISALLOW_COPY_AND_ASSIGN(SmsNotificationView);
283 };
284
285 TraySms::TraySms(SystemTray* system_tray)
286     : SystemTrayItem(system_tray),
287       default_(NULL),
288       detailed_(NULL),
289       notification_(NULL) {
290   // TODO(armansito): SMS could be a special case for cellular that requires a
291   // user (perhaps the owner) to be logged in. If that is the case, then an
292   // additional check should be done before subscribing for SMS notifications.
293   if (chromeos::NetworkHandler::IsInitialized())
294     chromeos::NetworkHandler::Get()->network_sms_handler()->AddObserver(this);
295 }
296
297 TraySms::~TraySms() {
298   if (chromeos::NetworkHandler::IsInitialized()) {
299     chromeos::NetworkHandler::Get()->network_sms_handler()->RemoveObserver(
300         this);
301   }
302 }
303
304 views::View* TraySms::CreateDefaultView(user::LoginStatus status) {
305   CHECK(default_ == NULL);
306   default_ = new SmsDefaultView(this);
307   default_->SetVisible(!messages_.empty());
308   return default_;
309 }
310
311 views::View* TraySms::CreateDetailedView(user::LoginStatus status) {
312   CHECK(detailed_ == NULL);
313   HideNotificationView();
314   if (messages_.empty())
315     return NULL;
316   detailed_ = new SmsDetailedView(this);
317   return detailed_;
318 }
319
320 views::View* TraySms::CreateNotificationView(user::LoginStatus status) {
321   CHECK(notification_ == NULL);
322   if (detailed_)
323     return NULL;
324   size_t index;
325   std::string number, text;
326   if (GetLatestMessage(&index, &number, &text))
327     notification_ = new SmsNotificationView(this, index, number, text);
328   return notification_;
329 }
330
331 void TraySms::DestroyDefaultView() {
332   default_ = NULL;
333 }
334
335 void TraySms::DestroyDetailedView() {
336   detailed_ = NULL;
337 }
338
339 void TraySms::DestroyNotificationView() {
340   notification_ = NULL;
341 }
342
343 void TraySms::MessageReceived(const base::DictionaryValue& message) {
344
345   std::string message_text;
346   if (!message.GetStringWithoutPathExpansion(
347           chromeos::NetworkSmsHandler::kTextKey, &message_text)) {
348     NET_LOG_ERROR("SMS message contains no content.", "");
349     return;
350   }
351   // TODO(armansito): A message might be due to a special "Message Waiting"
352   // state that the message is in. Once SMS handling moves to shill, such
353   // messages should be filtered there so that this check becomes unnecessary.
354   if (message_text.empty()) {
355     NET_LOG_DEBUG("SMS has empty content text. Ignoring.", "");
356     return;
357   }
358   std::string message_number;
359   if (!message.GetStringWithoutPathExpansion(
360           chromeos::NetworkSmsHandler::kNumberKey, &message_number)) {
361     NET_LOG_DEBUG("SMS contains no number. Ignoring.", "");
362     return;
363   }
364
365   NET_LOG_DEBUG("Received SMS from: " + message_number + " with text: " +
366                 message_text, "");
367
368   base::DictionaryValue* dict = new base::DictionaryValue();
369   dict->SetString(kSmsNumberKey, message_number);
370   dict->SetString(kSmsTextKey, message_text);
371   messages_.Append(dict);
372   Update(true);
373 }
374
375 bool TraySms::GetLatestMessage(size_t* index,
376                                std::string* number,
377                                std::string* text) {
378   if (messages_.empty())
379     return false;
380   base::DictionaryValue* message;
381   size_t message_index = messages_.GetSize() - 1;
382   if (!messages_.GetDictionary(message_index, &message))
383     return false;
384   if (!GetMessageFromDictionary(message, number, text))
385     return false;
386   *index = message_index;
387   return true;
388 }
389
390 void TraySms::RemoveMessage(size_t index) {
391   if (index < messages_.GetSize())
392     messages_.Remove(index, NULL);
393 }
394
395 void TraySms::Update(bool notify) {
396   if (messages_.empty()) {
397     if (default_)
398       default_->SetVisible(false);
399     if (detailed_)
400       HideDetailedView();
401     HideNotificationView();
402   } else {
403     if (default_) {
404       default_->SetVisible(true);
405       default_->Update();
406     }
407     if (detailed_)
408       detailed_->Update();
409     if (notification_) {
410       size_t index;
411       std::string number, text;
412       if (GetLatestMessage(&index, &number, &text))
413         notification_->Update(index, number, text);
414     } else if (notify) {
415       ShowNotificationView();
416     }
417   }
418 }
419
420 }  // namespace internal
421 }  // namespace ash