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))
58 class TraySms::SmsDefaultView : public TrayItemMore {
60 explicit SmsDefaultView(TraySms* owner)
61 : TrayItemMore(owner, true) {
62 SetImage(ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
63 IDR_AURA_UBER_TRAY_SMS));
67 virtual ~SmsDefaultView() {}
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));
74 SetAccessibleName(label);
78 DISALLOW_COPY_AND_ASSIGN(SmsDefaultView);
81 // An entry (row) in SmsDetailedView or NotificationView.
82 class TraySms::SmsMessageView : public views::View,
83 public views::ButtonListener {
90 SmsMessageView(TraySms* owner,
93 const std::string& number,
94 const std::string& message)
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);
104 message_label_ = new views::Label(base::UTF8ToUTF16(message));
105 message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
106 message_label_->SetMultiLine(true);
108 if (view_type == VIEW_DETAILED)
109 LayoutDetailedView();
111 LayoutNotificationView();
114 virtual ~SmsMessageView() {
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);
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);
136 views::GridLayout* layout = new views::GridLayout(this);
137 SetLayoutManager(layout);
139 views::ColumnSet* columns = layout->AddColumnSet(0);
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);
148 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
149 0, /* resize percent */
150 views::GridLayout::FIXED,
151 kNotificationIconWidth, kNotificationIconWidth);
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_);
161 layout->AddPaddingRow(0, kPaddingVertical);
164 void LayoutNotificationView() {
166 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
167 AddChildView(number_label_);
168 message_label_->SizeToFit(kTrayNotificationContentsWidth);
169 AddChildView(message_label_);
174 views::Label* number_label_;
175 views::Label* message_label_;
177 DISALLOW_COPY_AND_ASSIGN(SmsMessageView);
180 class TraySms::SmsDetailedView : public TrayDetailsView,
181 public ViewClickListener {
183 explicit SmsDetailedView(TraySms* owner)
184 : TrayDetailsView(owner) {
189 virtual ~SmsDetailedView() {
193 CreateScrollableList();
194 CreateSpecialRow(IDS_ASH_STATUS_TRAY_SMS, this);
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;
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;
222 std::string number, text;
223 if (!GetMessageFromDictionary(message, &number, &text)) {
224 LOG(ERROR) << "Error parsing SMS message";
227 SmsMessageView* msgview = new SmsMessageView(
228 static_cast<TraySms*>(owner()), SmsMessageView::VIEW_DETAILED, index,
230 scroll_content()->AddChildView(msgview);
232 scroller()->Layout();
235 // Overridden from ViewClickListener.
236 virtual void OnViewClicked(views::View* sender) OVERRIDE {
237 if (sender == footer()->content())
238 TransitionToDefaultView();
241 DISALLOW_COPY_AND_ASSIGN(SmsDetailedView);
244 class TraySms::SmsNotificationView : public TrayNotificationView {
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);
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);
266 // Overridden from TrayNotificationView:
267 virtual void OnClose() OVERRIDE {
268 tray_sms()->RemoveMessage(message_index_);
271 virtual void OnClickAction() OVERRIDE {
272 owner()->PopupDetailedView(0, true);
276 TraySms* tray_sms() {
277 return static_cast<TraySms*>(owner());
280 size_t message_index_;
282 DISALLOW_COPY_AND_ASSIGN(SmsNotificationView);
285 TraySms::TraySms(SystemTray* system_tray)
286 : SystemTrayItem(system_tray),
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);
297 TraySms::~TraySms() {
298 if (chromeos::NetworkHandler::IsInitialized()) {
299 chromeos::NetworkHandler::Get()->network_sms_handler()->RemoveObserver(
304 views::View* TraySms::CreateDefaultView(user::LoginStatus status) {
305 CHECK(default_ == NULL);
306 default_ = new SmsDefaultView(this);
307 default_->SetVisible(!messages_.empty());
311 views::View* TraySms::CreateDetailedView(user::LoginStatus status) {
312 CHECK(detailed_ == NULL);
313 HideNotificationView();
314 if (messages_.empty())
316 detailed_ = new SmsDetailedView(this);
320 views::View* TraySms::CreateNotificationView(user::LoginStatus status) {
321 CHECK(notification_ == NULL);
325 std::string number, text;
326 if (GetLatestMessage(&index, &number, &text))
327 notification_ = new SmsNotificationView(this, index, number, text);
328 return notification_;
331 void TraySms::DestroyDefaultView() {
335 void TraySms::DestroyDetailedView() {
339 void TraySms::DestroyNotificationView() {
340 notification_ = NULL;
343 void TraySms::MessageReceived(const base::DictionaryValue& message) {
345 std::string message_text;
346 if (!message.GetStringWithoutPathExpansion(
347 chromeos::NetworkSmsHandler::kTextKey, &message_text)) {
348 NET_LOG_ERROR("SMS message contains no content.", "");
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.", "");
358 std::string message_number;
359 if (!message.GetStringWithoutPathExpansion(
360 chromeos::NetworkSmsHandler::kNumberKey, &message_number)) {
361 NET_LOG_DEBUG("SMS contains no number. Ignoring.", "");
365 NET_LOG_DEBUG("Received SMS from: " + message_number + " with text: " +
368 base::DictionaryValue* dict = new base::DictionaryValue();
369 dict->SetString(kSmsNumberKey, message_number);
370 dict->SetString(kSmsTextKey, message_text);
371 messages_.Append(dict);
375 bool TraySms::GetLatestMessage(size_t* index,
378 if (messages_.empty())
380 base::DictionaryValue* message;
381 size_t message_index = messages_.GetSize() - 1;
382 if (!messages_.GetDictionary(message_index, &message))
384 if (!GetMessageFromDictionary(message, number, text))
386 *index = message_index;
390 void TraySms::RemoveMessage(size_t index) {
391 if (index < messages_.GetSize())
392 messages_.Remove(index, NULL);
395 void TraySms::Update(bool notify) {
396 if (messages_.empty()) {
398 default_->SetVisible(false);
401 HideNotificationView();
404 default_->SetVisible(true);
411 std::string number, text;
412 if (GetLatestMessage(&index, &number, &text))
413 notification_->Update(index, number, text);
415 ShowNotificationView();
420 } // namespace internal