Elim cr-checkbox
[chromium-blink-merge.git] / ash / system / chromeos / network / tray_sms.cc
blob144a4cc5f9e96bc1f021f8a2a1a41b70158dff64
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"
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"
33 namespace {
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,
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;
53 } // namespace
55 namespace ash {
57 class TraySms::SmsDefaultView : public TrayItemMore {
58 public:
59 explicit SmsDefaultView(TraySms* owner)
60 : TrayItemMore(owner, true) {
61 SetImage(ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
62 IDR_AURA_UBER_TRAY_SMS));
63 Update();
66 ~SmsDefaultView() override {}
68 void Update() {
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));
72 SetLabel(label);
73 SetAccessibleName(label);
76 private:
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 {
83 public:
84 enum ViewType {
85 VIEW_DETAILED,
86 VIEW_NOTIFICATION
89 SmsMessageView(TraySms* owner,
90 ViewType view_type,
91 size_t index,
92 const std::string& number,
93 const std::string& message)
94 : owner_(owner),
95 index_(index) {
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();
109 else
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);
121 private:
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);
138 // Message
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);
144 // Close button
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() {
162 SetLayoutManager(
163 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
164 AddChildView(number_label_);
165 message_label_->SizeToFit(kTrayNotificationContentsWidth);
166 AddChildView(message_label_);
169 TraySms* owner_;
170 size_t index_;
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 {
179 public:
180 explicit SmsDetailedView(TraySms* owner)
181 : TrayDetailsView(owner) {
182 Init();
183 Update();
186 ~SmsDetailedView() override {}
188 void Init() {
189 CreateScrollableList();
190 CreateSpecialRow(IDS_ASH_STATUS_TRAY_SMS, this);
193 void Update() {
194 UpdateMessageList();
195 Layout();
196 SchedulePaint();
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;
207 private:
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;
216 continue;
218 std::string number, text;
219 if (!GetMessageFromDictionary(message, &number, &text)) {
220 LOG(ERROR) << "Error parsing SMS message";
221 continue;
223 SmsMessageView* msgview = new SmsMessageView(
224 static_cast<TraySms*>(owner()), SmsMessageView::VIEW_DETAILED, index,
225 number, text);
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 {
241 public:
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); }
267 private:
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),
279 default_(NULL),
280 detailed_(NULL),
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(
292 this);
296 views::View* TraySms::CreateDefaultView(user::LoginStatus status) {
297 CHECK(default_ == NULL);
298 default_ = new SmsDefaultView(this);
299 default_->SetVisible(!messages_.empty());
300 return default_;
303 views::View* TraySms::CreateDetailedView(user::LoginStatus status) {
304 CHECK(detailed_ == NULL);
305 HideNotificationView();
306 if (messages_.empty())
307 return NULL;
308 detailed_ = new SmsDetailedView(this);
309 return detailed_;
312 views::View* TraySms::CreateNotificationView(user::LoginStatus status) {
313 CHECK(notification_ == NULL);
314 if (detailed_)
315 return NULL;
316 size_t index;
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() {
324 default_ = NULL;
327 void TraySms::DestroyDetailedView() {
328 detailed_ = NULL;
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.", "");
341 return;
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.", "");
348 return;
350 std::string message_number;
351 if (!message.GetStringWithoutPathExpansion(
352 chromeos::NetworkSmsHandler::kNumberKey, &message_number)) {
353 NET_LOG_DEBUG("SMS contains no number. Ignoring.", "");
354 return;
357 NET_LOG_DEBUG("Received SMS from: " + message_number + " with text: " +
358 message_text, "");
360 base::DictionaryValue* dict = new base::DictionaryValue();
361 dict->SetString(kSmsNumberKey, message_number);
362 dict->SetString(kSmsTextKey, message_text);
363 messages_.Append(dict);
364 Update(true);
367 bool TraySms::GetLatestMessage(size_t* index,
368 std::string* number,
369 std::string* text) {
370 if (messages_.empty())
371 return false;
372 base::DictionaryValue* message;
373 size_t message_index = messages_.GetSize() - 1;
374 if (!messages_.GetDictionary(message_index, &message))
375 return false;
376 if (!GetMessageFromDictionary(message, number, text))
377 return false;
378 *index = message_index;
379 return true;
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()) {
389 if (default_)
390 default_->SetVisible(false);
391 if (detailed_)
392 HideDetailedView();
393 HideNotificationView();
394 } else {
395 if (default_) {
396 default_->SetVisible(true);
397 default_->Update();
399 if (detailed_)
400 detailed_->Update();
401 if (notification_) {
402 size_t index;
403 std::string number, text;
404 if (GetLatestMessage(&index, &number, &text))
405 notification_->Update(index, number, text);
406 } else if (notify) {
407 ShowNotificationView();
412 } // namespace ash