OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "ash/system/chromeos/network/tray_sms.h" | |
6 | |
7 #include "ash/common/system/tray/fixed_sized_scroll_view.h" | |
8 #include "ash/common/system/tray/system_tray_bubble.h" | |
9 #include "ash/common/system/tray/tray_constants.h" | |
10 #include "ash/common/system/tray/tray_details_view.h" | |
11 #include "ash/common/system/tray/tray_item_more.h" | |
12 #include "ash/common/system/tray/tray_item_view.h" | |
13 #include "ash/common/system/tray/tray_notification_view.h" | |
14 #include "ash/common/system/tray/view_click_listener.h" | |
15 #include "ash/shell.h" | |
16 #include "ash/system/tray/system_tray.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" | |
32 | |
33 namespace { | |
34 | |
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; | |
39 | |
40 const char kSmsNumberKey[] = "number"; | |
41 const char kSmsTextKey[] = "text"; | |
42 | |
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; | |
51 } | |
52 | |
53 } // namespace | |
54 | |
55 namespace ash { | |
56 | |
57 class TraySms::SmsDefaultView : public TrayItemMore { | |
58 public: | |
59 explicit SmsDefaultView(TraySms* owner) : TrayItemMore(owner, true) { | |
60 SetImage(ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( | |
61 IDR_AURA_UBER_TRAY_SMS)); | |
62 Update(); | |
63 } | |
64 | |
65 ~SmsDefaultView() override {} | |
66 | |
67 void Update() { | |
68 int message_count = static_cast<TraySms*>(owner())->messages().GetSize(); | |
69 // TODO(jshin): Currently, a tabular format is used ("SMS Messages: | |
70 // <count>"). Check with UX if '<count> SMS messages' with a proper plural | |
71 // support is desired. | |
72 base::string16 label = l10n_util::GetStringFUTF16Int( | |
73 IDS_ASH_STATUS_TRAY_SMS_MESSAGES, message_count); | |
74 SetLabel(label); | |
75 SetAccessibleName(label); | |
76 } | |
77 | |
78 private: | |
79 DISALLOW_COPY_AND_ASSIGN(SmsDefaultView); | |
80 }; | |
81 | |
82 // An entry (row) in SmsDetailedView or NotificationView. | |
83 class TraySms::SmsMessageView : public views::View, | |
84 public views::ButtonListener { | |
85 public: | |
86 enum ViewType { VIEW_DETAILED, VIEW_NOTIFICATION }; | |
87 | |
88 SmsMessageView(TraySms* owner, | |
89 ViewType view_type, | |
90 size_t index, | |
91 const std::string& number, | |
92 const std::string& message) | |
93 : owner_(owner), index_(index) { | |
94 // TODO(jshin): Convert ASCII digits in |number| (phone number) to native | |
95 // digits if necessary. |number| can contain non-digit characters and may | |
96 // have to be converted one-by-one or use libphonenumber's formating API. | |
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); | |
103 | |
104 message_label_ = new views::Label(base::UTF8ToUTF16(message)); | |
105 message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
106 message_label_->SetMultiLine(true); | |
107 | |
108 if (view_type == VIEW_DETAILED) | |
109 LayoutDetailedView(); | |
110 else | |
111 LayoutNotificationView(); | |
112 } | |
113 | |
114 ~SmsMessageView() override {} | |
115 | |
116 // Overridden from ButtonListener. | |
117 void ButtonPressed(views::Button* sender, const ui::Event& event) override { | |
118 owner_->RemoveMessage(index_); | |
119 owner_->Update(false); | |
120 } | |
121 | |
122 private: | |
123 void LayoutDetailedView() { | |
124 views::ImageButton* close_button = new views::ImageButton(this); | |
125 close_button->SetImage( | |
126 views::CustomButton::STATE_NORMAL, | |
127 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( | |
128 IDR_AURA_UBER_TRAY_SMS_DISMISS)); | |
129 const int msg_width = | |
130 owner_->system_tray() | |
131 ->GetSystemBubble() | |
132 ->bubble_view() | |
133 ->GetPreferredSize() | |
134 .width() - | |
135 (kNotificationIconWidth + kTrayPopupPaddingHorizontal * 2); | |
136 message_label_->SizeToFit(msg_width); | |
137 | |
138 views::GridLayout* layout = new views::GridLayout(this); | |
139 SetLayoutManager(layout); | |
140 | |
141 views::ColumnSet* columns = layout->AddColumnSet(0); | |
142 | |
143 // Message | |
144 columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal); | |
145 columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, | |
146 0 /* resize percent */, views::GridLayout::FIXED, | |
147 msg_width, msg_width); | |
148 | |
149 // Close button | |
150 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, | |
151 0, /* resize percent */ | |
152 views::GridLayout::FIXED, kNotificationIconWidth, | |
153 kNotificationIconWidth); | |
154 | |
155 layout->AddPaddingRow(0, kPaddingVertical); | |
156 layout->StartRow(0, 0); | |
157 layout->AddView(number_label_); | |
158 layout->AddView(close_button, 1, 2); // 2 rows for icon | |
159 layout->StartRow(0, 0); | |
160 layout->AddView(message_label_); | |
161 | |
162 layout->AddPaddingRow(0, kPaddingVertical); | |
163 } | |
164 | |
165 void LayoutNotificationView() { | |
166 SetLayoutManager( | |
167 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1)); | |
168 AddChildView(number_label_); | |
169 message_label_->SizeToFit(kTrayNotificationContentsWidth); | |
170 AddChildView(message_label_); | |
171 } | |
172 | |
173 TraySms* owner_; | |
174 size_t index_; | |
175 views::Label* number_label_; | |
176 views::Label* message_label_; | |
177 | |
178 DISALLOW_COPY_AND_ASSIGN(SmsMessageView); | |
179 }; | |
180 | |
181 class TraySms::SmsDetailedView : public TrayDetailsView, | |
182 public ViewClickListener { | |
183 public: | |
184 explicit SmsDetailedView(TraySms* owner) : TrayDetailsView(owner) { | |
185 Init(); | |
186 Update(); | |
187 } | |
188 | |
189 ~SmsDetailedView() override {} | |
190 | |
191 void Init() { | |
192 CreateScrollableList(); | |
193 CreateSpecialRow(IDS_ASH_STATUS_TRAY_SMS, this); | |
194 } | |
195 | |
196 void Update() { | |
197 UpdateMessageList(); | |
198 Layout(); | |
199 SchedulePaint(); | |
200 } | |
201 | |
202 // Overridden from views::View. | |
203 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; | |
208 } | |
209 | |
210 private: | |
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; | |
219 continue; | |
220 } | |
221 std::string number, text; | |
222 if (!GetMessageFromDictionary(message, &number, &text)) { | |
223 LOG(ERROR) << "Error parsing SMS message"; | |
224 continue; | |
225 } | |
226 SmsMessageView* msgview = new SmsMessageView( | |
227 static_cast<TraySms*>(owner()), SmsMessageView::VIEW_DETAILED, index, | |
228 number, text); | |
229 scroll_content()->AddChildView(msgview); | |
230 } | |
231 scroller()->Layout(); | |
232 } | |
233 | |
234 // Overridden from ViewClickListener. | |
235 void OnViewClicked(views::View* sender) override { | |
236 if (sender == footer()->content()) | |
237 TransitionToDefaultView(); | |
238 } | |
239 | |
240 DISALLOW_COPY_AND_ASSIGN(SmsDetailedView); | |
241 }; | |
242 | |
243 class TraySms::SmsNotificationView : public TrayNotificationView { | |
244 public: | |
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); | |
254 } | |
255 | |
256 void Update(size_t message_index, | |
257 const std::string& number, | |
258 const std::string& text) { | |
259 SmsMessageView* message_view = | |
260 new SmsMessageView(tray_sms(), SmsMessageView::VIEW_NOTIFICATION, | |
261 message_index_, number, text); | |
262 UpdateView(message_view); | |
263 } | |
264 | |
265 // Overridden from TrayNotificationView: | |
266 void OnClose() override { tray_sms()->RemoveMessage(message_index_); } | |
267 | |
268 void OnClickAction() override { owner()->PopupDetailedView(0, true); } | |
269 | |
270 private: | |
271 TraySms* tray_sms() { return static_cast<TraySms*>(owner()); } | |
272 | |
273 size_t message_index_; | |
274 | |
275 DISALLOW_COPY_AND_ASSIGN(SmsNotificationView); | |
276 }; | |
277 | |
278 TraySms::TraySms(SystemTray* system_tray) | |
279 : SystemTrayItem(system_tray), | |
280 default_(NULL), | |
281 detailed_(NULL), | |
282 notification_(NULL) { | |
283 // TODO(armansito): SMS could be a special case for cellular that requires a | |
284 // user (perhaps the owner) to be logged in. If that is the case, then an | |
285 // additional check should be done before subscribing for SMS notifications. | |
286 if (chromeos::NetworkHandler::IsInitialized()) | |
287 chromeos::NetworkHandler::Get()->network_sms_handler()->AddObserver(this); | |
288 } | |
289 | |
290 TraySms::~TraySms() { | |
291 if (chromeos::NetworkHandler::IsInitialized()) { | |
292 chromeos::NetworkHandler::Get()->network_sms_handler()->RemoveObserver( | |
293 this); | |
294 } | |
295 } | |
296 | |
297 views::View* TraySms::CreateDefaultView(LoginStatus status) { | |
298 CHECK(default_ == NULL); | |
299 default_ = new SmsDefaultView(this); | |
300 default_->SetVisible(!messages_.empty()); | |
301 return default_; | |
302 } | |
303 | |
304 views::View* TraySms::CreateDetailedView(LoginStatus status) { | |
305 CHECK(detailed_ == NULL); | |
306 HideNotificationView(); | |
307 if (messages_.empty()) | |
308 return NULL; | |
309 detailed_ = new SmsDetailedView(this); | |
310 return detailed_; | |
311 } | |
312 | |
313 views::View* TraySms::CreateNotificationView(LoginStatus status) { | |
314 CHECK(notification_ == NULL); | |
315 if (detailed_) | |
316 return NULL; | |
317 size_t index; | |
318 std::string number, text; | |
319 if (GetLatestMessage(&index, &number, &text)) | |
320 notification_ = new SmsNotificationView(this, index, number, text); | |
321 return notification_; | |
322 } | |
323 | |
324 void TraySms::DestroyDefaultView() { | |
325 default_ = NULL; | |
326 } | |
327 | |
328 void TraySms::DestroyDetailedView() { | |
329 detailed_ = NULL; | |
330 } | |
331 | |
332 void TraySms::DestroyNotificationView() { | |
333 notification_ = NULL; | |
334 } | |
335 | |
336 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; | |
342 } | |
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; | |
349 } | |
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; | |
355 } | |
356 | |
357 NET_LOG_DEBUG( | |
358 "Received SMS from: " + message_number + " with text: " + message_text, | |
359 ""); | |
360 | |
361 base::DictionaryValue* dict = new base::DictionaryValue(); | |
362 dict->SetString(kSmsNumberKey, message_number); | |
363 dict->SetString(kSmsTextKey, message_text); | |
364 messages_.Append(dict); | |
365 Update(true); | |
366 } | |
367 | |
368 bool TraySms::GetLatestMessage(size_t* index, | |
369 std::string* number, | |
370 std::string* text) { | |
371 if (messages_.empty()) | |
372 return false; | |
373 base::DictionaryValue* message; | |
374 size_t message_index = messages_.GetSize() - 1; | |
375 if (!messages_.GetDictionary(message_index, &message)) | |
376 return false; | |
377 if (!GetMessageFromDictionary(message, number, text)) | |
378 return false; | |
379 *index = message_index; | |
380 return true; | |
381 } | |
382 | |
383 void TraySms::RemoveMessage(size_t index) { | |
384 if (index < messages_.GetSize()) | |
385 messages_.Remove(index, NULL); | |
386 } | |
387 | |
388 void TraySms::Update(bool notify) { | |
389 if (messages_.empty()) { | |
390 if (default_) | |
391 default_->SetVisible(false); | |
392 if (detailed_) | |
393 HideDetailedView(); | |
394 HideNotificationView(); | |
395 } else { | |
396 if (default_) { | |
397 default_->SetVisible(true); | |
398 default_->Update(); | |
399 } | |
400 if (detailed_) | |
401 detailed_->Update(); | |
402 if (notification_) { | |
403 size_t index; | |
404 std::string number, text; | |
405 if (GetLatestMessage(&index, &number, &text)) | |
406 notification_->Update(index, number, text); | |
407 } else if (notify) { | |
408 ShowNotificationView(); | |
409 } | |
410 } | |
411 } | |
412 | |
413 } // namespace ash | |
OLD | NEW |