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.
5 #include "ash/system/chromeos/network/tray_sms.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"
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;
40 const char kSmsNumberKey[] = "number";
41 const char kSmsTextKey[] = "text";
43 bool GetMessageFromDictionary(const base::DictionaryValue* message,
46 if (!message->GetStringWithoutPathExpansion(kSmsNumberKey, number))
48 if (!message->GetStringWithoutPathExpansion(kSmsTextKey, text))
57 class TraySms::SmsDefaultView : public TrayItemMore {
59 explicit SmsDefaultView(TraySms* owner)
60 : TrayItemMore(owner, true) {
61 SetImage(ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
62 IDR_AURA_UBER_TRAY_SMS));
66 virtual ~SmsDefaultView() {}
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));
73 SetAccessibleName(label);
77 DISALLOW_COPY_AND_ASSIGN(SmsDefaultView);
80 // An entry (row) in SmsDetailedView or NotificationView.
81 class TraySms::SmsMessageView : public views::View,
82 public views::ButtonListener {
89 SmsMessageView(TraySms* owner,
92 const std::string& number,
93 const std::string& message)
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);
103 message_label_ = new views::Label(base::UTF8ToUTF16(message));
104 message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
105 message_label_->SetMultiLine(true);
107 if (view_type == VIEW_DETAILED)
108 LayoutDetailedView();
110 LayoutNotificationView();
113 virtual ~SmsMessageView() {
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);
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);
135 views::GridLayout* layout = new views::GridLayout(this);
136 SetLayoutManager(layout);
138 views::ColumnSet* columns = layout->AddColumnSet(0);
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);
147 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
148 0, /* resize percent */
149 views::GridLayout::FIXED,
150 kNotificationIconWidth, kNotificationIconWidth);
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_);
160 layout->AddPaddingRow(0, kPaddingVertical);
163 void LayoutNotificationView() {
165 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
166 AddChildView(number_label_);
167 message_label_->SizeToFit(kTrayNotificationContentsWidth);
168 AddChildView(message_label_);
173 views::Label* number_label_;
174 views::Label* message_label_;
176 DISALLOW_COPY_AND_ASSIGN(SmsMessageView);
179 class TraySms::SmsDetailedView : public TrayDetailsView,
180 public ViewClickListener {
182 explicit SmsDetailedView(TraySms* owner)
183 : TrayDetailsView(owner) {
188 virtual ~SmsDetailedView() {
192 CreateScrollableList();
193 CreateSpecialRow(IDS_ASH_STATUS_TRAY_SMS, this);
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;
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;
221 std::string number, text;
222 if (!GetMessageFromDictionary(message, &number, &text)) {
223 LOG(ERROR) << "Error parsing SMS message";
226 SmsMessageView* msgview = new SmsMessageView(
227 static_cast<TraySms*>(owner()), SmsMessageView::VIEW_DETAILED, index,
229 scroll_content()->AddChildView(msgview);
231 scroller()->Layout();
234 // Overridden from ViewClickListener.
235 virtual void OnViewClicked(views::View* sender) OVERRIDE {
236 if (sender == footer()->content())
237 TransitionToDefaultView();
240 DISALLOW_COPY_AND_ASSIGN(SmsDetailedView);
243 class TraySms::SmsNotificationView : public TrayNotificationView {
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);
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);
265 // Overridden from TrayNotificationView:
266 virtual void OnClose() OVERRIDE {
267 tray_sms()->RemoveMessage(message_index_);
270 virtual void OnClickAction() OVERRIDE {
271 owner()->PopupDetailedView(0, true);
275 TraySms* tray_sms() {
276 return static_cast<TraySms*>(owner());
279 size_t message_index_;
281 DISALLOW_COPY_AND_ASSIGN(SmsNotificationView);
284 TraySms::TraySms(SystemTray* system_tray)
285 : SystemTrayItem(system_tray),
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);
296 TraySms::~TraySms() {
297 if (chromeos::NetworkHandler::IsInitialized()) {
298 chromeos::NetworkHandler::Get()->network_sms_handler()->RemoveObserver(
303 views::View* TraySms::CreateDefaultView(user::LoginStatus status) {
304 CHECK(default_ == NULL);
305 default_ = new SmsDefaultView(this);
306 default_->SetVisible(!messages_.empty());
310 views::View* TraySms::CreateDetailedView(user::LoginStatus status) {
311 CHECK(detailed_ == NULL);
312 HideNotificationView();
313 if (messages_.empty())
315 detailed_ = new SmsDetailedView(this);
319 views::View* TraySms::CreateNotificationView(user::LoginStatus status) {
320 CHECK(notification_ == NULL);
324 std::string number, text;
325 if (GetLatestMessage(&index, &number, &text))
326 notification_ = new SmsNotificationView(this, index, number, text);
327 return notification_;
330 void TraySms::DestroyDefaultView() {
334 void TraySms::DestroyDetailedView() {
338 void TraySms::DestroyNotificationView() {
339 notification_ = NULL;
342 void TraySms::MessageReceived(const base::DictionaryValue& message) {
344 std::string message_text;
345 if (!message.GetStringWithoutPathExpansion(
346 chromeos::NetworkSmsHandler::kTextKey, &message_text)) {
347 NET_LOG_ERROR("SMS message contains no content.", "");
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.", "");
357 std::string message_number;
358 if (!message.GetStringWithoutPathExpansion(
359 chromeos::NetworkSmsHandler::kNumberKey, &message_number)) {
360 NET_LOG_DEBUG("SMS contains no number. Ignoring.", "");
364 NET_LOG_DEBUG("Received SMS from: " + message_number + " with text: " +
367 base::DictionaryValue* dict = new base::DictionaryValue();
368 dict->SetString(kSmsNumberKey, message_number);
369 dict->SetString(kSmsTextKey, message_text);
370 messages_.Append(dict);
374 bool TraySms::GetLatestMessage(size_t* index,
377 if (messages_.empty())
379 base::DictionaryValue* message;
380 size_t message_index = messages_.GetSize() - 1;
381 if (!messages_.GetDictionary(message_index, &message))
383 if (!GetMessageFromDictionary(message, number, text))
385 *index = message_index;
389 void TraySms::RemoveMessage(size_t index) {
390 if (index < messages_.GetSize())
391 messages_.Remove(index, NULL);
394 void TraySms::Update(bool notify) {
395 if (messages_.empty()) {
397 default_->SetVisible(false);
400 HideNotificationView();
403 default_->SetVisible(true);
410 std::string number, text;
411 if (GetLatestMessage(&index, &number, &text))
412 notification_->Update(index, number, text);
414 ShowNotificationView();