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 ~SmsDefaultView() override
{}
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 ~SmsMessageView() override
{}
115 // Overridden from ButtonListener.
116 void ButtonPressed(views::Button
* sender
, const ui::Event
& event
) override
{
117 owner_
->RemoveMessage(index_
);
118 owner_
->Update(false);
122 void LayoutDetailedView() {
123 views::ImageButton
* close_button
= new views::ImageButton(this);
124 close_button
->SetImage(
125 views::CustomButton::STATE_NORMAL
,
126 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
127 IDR_AURA_UBER_TRAY_SMS_DISMISS
));
128 const int msg_width
= owner_
->system_tray()->GetSystemBubble()->
129 bubble_view()->GetPreferredSize().width() -
130 (kNotificationIconWidth
+ kTrayPopupPaddingHorizontal
* 2);
131 message_label_
->SizeToFit(msg_width
);
133 views::GridLayout
* layout
= new views::GridLayout(this);
134 SetLayoutManager(layout
);
136 views::ColumnSet
* columns
= layout
->AddColumnSet(0);
139 columns
->AddPaddingColumn(0, kTrayPopupPaddingHorizontal
);
140 columns
->AddColumn(views::GridLayout::FILL
, views::GridLayout::FILL
,
141 0 /* resize percent */,
142 views::GridLayout::FIXED
, msg_width
, msg_width
);
145 columns
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::CENTER
,
146 0, /* resize percent */
147 views::GridLayout::FIXED
,
148 kNotificationIconWidth
, kNotificationIconWidth
);
151 layout
->AddPaddingRow(0, kPaddingVertical
);
152 layout
->StartRow(0, 0);
153 layout
->AddView(number_label_
);
154 layout
->AddView(close_button
, 1, 2); // 2 rows for icon
155 layout
->StartRow(0, 0);
156 layout
->AddView(message_label_
);
158 layout
->AddPaddingRow(0, kPaddingVertical
);
161 void LayoutNotificationView() {
163 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 1));
164 AddChildView(number_label_
);
165 message_label_
->SizeToFit(kTrayNotificationContentsWidth
);
166 AddChildView(message_label_
);
171 views::Label
* number_label_
;
172 views::Label
* message_label_
;
174 DISALLOW_COPY_AND_ASSIGN(SmsMessageView
);
177 class TraySms::SmsDetailedView
: public TrayDetailsView
,
178 public ViewClickListener
{
180 explicit SmsDetailedView(TraySms
* owner
)
181 : TrayDetailsView(owner
) {
186 ~SmsDetailedView() override
{}
189 CreateScrollableList();
190 CreateSpecialRow(IDS_ASH_STATUS_TRAY_SMS
, this);
199 // Overridden from views::View.
200 gfx::Size
GetPreferredSize() const override
{
201 gfx::Size preferred_size
= TrayDetailsView::GetPreferredSize();
202 if (preferred_size
.height() < kMessageListMinHeight
)
203 preferred_size
.set_height(kMessageListMinHeight
);
204 return preferred_size
;
208 void UpdateMessageList() {
209 const base::ListValue
& messages
=
210 static_cast<TraySms
*>(owner())->messages();
211 scroll_content()->RemoveAllChildViews(true);
212 for (size_t index
= 0; index
< messages
.GetSize(); ++index
) {
213 const base::DictionaryValue
* message
= NULL
;
214 if (!messages
.GetDictionary(index
, &message
)) {
215 LOG(ERROR
) << "SMS message not a dictionary at: " << index
;
218 std::string number
, text
;
219 if (!GetMessageFromDictionary(message
, &number
, &text
)) {
220 LOG(ERROR
) << "Error parsing SMS message";
223 SmsMessageView
* msgview
= new SmsMessageView(
224 static_cast<TraySms
*>(owner()), SmsMessageView::VIEW_DETAILED
, index
,
226 scroll_content()->AddChildView(msgview
);
228 scroller()->Layout();
231 // Overridden from ViewClickListener.
232 void OnViewClicked(views::View
* sender
) override
{
233 if (sender
== footer()->content())
234 TransitionToDefaultView();
237 DISALLOW_COPY_AND_ASSIGN(SmsDetailedView
);
240 class TraySms::SmsNotificationView
: public TrayNotificationView
{
242 SmsNotificationView(TraySms
* owner
,
243 size_t message_index
,
244 const std::string
& number
,
245 const std::string
& text
)
246 : TrayNotificationView(owner
, IDR_AURA_UBER_TRAY_SMS
),
247 message_index_(message_index
) {
248 SmsMessageView
* message_view
= new SmsMessageView(
249 owner
, SmsMessageView::VIEW_NOTIFICATION
, message_index_
, number
, text
);
250 InitView(message_view
);
253 void Update(size_t message_index
,
254 const std::string
& number
,
255 const std::string
& text
) {
256 SmsMessageView
* message_view
= new SmsMessageView(
257 tray_sms(), SmsMessageView::VIEW_NOTIFICATION
,
258 message_index_
, number
, text
);
259 UpdateView(message_view
);
262 // Overridden from TrayNotificationView:
263 void OnClose() override
{ tray_sms()->RemoveMessage(message_index_
); }
265 void OnClickAction() override
{ owner()->PopupDetailedView(0, true); }
268 TraySms
* tray_sms() {
269 return static_cast<TraySms
*>(owner());
272 size_t message_index_
;
274 DISALLOW_COPY_AND_ASSIGN(SmsNotificationView
);
277 TraySms::TraySms(SystemTray
* system_tray
)
278 : SystemTrayItem(system_tray
),
281 notification_(NULL
) {
282 // TODO(armansito): SMS could be a special case for cellular that requires a
283 // user (perhaps the owner) to be logged in. If that is the case, then an
284 // additional check should be done before subscribing for SMS notifications.
285 if (chromeos::NetworkHandler::IsInitialized())
286 chromeos::NetworkHandler::Get()->network_sms_handler()->AddObserver(this);
289 TraySms::~TraySms() {
290 if (chromeos::NetworkHandler::IsInitialized()) {
291 chromeos::NetworkHandler::Get()->network_sms_handler()->RemoveObserver(
296 views::View
* TraySms::CreateDefaultView(user::LoginStatus status
) {
297 CHECK(default_
== NULL
);
298 default_
= new SmsDefaultView(this);
299 default_
->SetVisible(!messages_
.empty());
303 views::View
* TraySms::CreateDetailedView(user::LoginStatus status
) {
304 CHECK(detailed_
== NULL
);
305 HideNotificationView();
306 if (messages_
.empty())
308 detailed_
= new SmsDetailedView(this);
312 views::View
* TraySms::CreateNotificationView(user::LoginStatus status
) {
313 CHECK(notification_
== NULL
);
317 std::string number
, text
;
318 if (GetLatestMessage(&index
, &number
, &text
))
319 notification_
= new SmsNotificationView(this, index
, number
, text
);
320 return notification_
;
323 void TraySms::DestroyDefaultView() {
327 void TraySms::DestroyDetailedView() {
331 void TraySms::DestroyNotificationView() {
332 notification_
= NULL
;
335 void TraySms::MessageReceived(const base::DictionaryValue
& message
) {
337 std::string message_text
;
338 if (!message
.GetStringWithoutPathExpansion(
339 chromeos::NetworkSmsHandler::kTextKey
, &message_text
)) {
340 NET_LOG_ERROR("SMS message contains no content.", "");
343 // TODO(armansito): A message might be due to a special "Message Waiting"
344 // state that the message is in. Once SMS handling moves to shill, such
345 // messages should be filtered there so that this check becomes unnecessary.
346 if (message_text
.empty()) {
347 NET_LOG_DEBUG("SMS has empty content text. Ignoring.", "");
350 std::string message_number
;
351 if (!message
.GetStringWithoutPathExpansion(
352 chromeos::NetworkSmsHandler::kNumberKey
, &message_number
)) {
353 NET_LOG_DEBUG("SMS contains no number. Ignoring.", "");
357 NET_LOG_DEBUG("Received SMS from: " + message_number
+ " with text: " +
360 base::DictionaryValue
* dict
= new base::DictionaryValue();
361 dict
->SetString(kSmsNumberKey
, message_number
);
362 dict
->SetString(kSmsTextKey
, message_text
);
363 messages_
.Append(dict
);
367 bool TraySms::GetLatestMessage(size_t* index
,
370 if (messages_
.empty())
372 base::DictionaryValue
* message
;
373 size_t message_index
= messages_
.GetSize() - 1;
374 if (!messages_
.GetDictionary(message_index
, &message
))
376 if (!GetMessageFromDictionary(message
, number
, text
))
378 *index
= message_index
;
382 void TraySms::RemoveMessage(size_t index
) {
383 if (index
< messages_
.GetSize())
384 messages_
.Remove(index
, NULL
);
387 void TraySms::Update(bool notify
) {
388 if (messages_
.empty()) {
390 default_
->SetVisible(false);
393 HideNotificationView();
396 default_
->SetVisible(true);
403 std::string number
, text
;
404 if (GetLatestMessage(&index
, &number
, &text
))
405 notification_
->Update(index
, number
, text
);
407 ShowNotificationView();