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() 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
;
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();