tray_sms.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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/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" 32 33namespace { 34 35// Min height of the list of messages in the popup. 36const int kMessageListMinHeight = 200; 37// Top/bottom padding of the text items. 38const int kPaddingVertical = 10; 39 40const char kSmsNumberKey[] = "number"; 41const char kSmsTextKey[] = "text"; 42 43bool 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 55namespace ash { 56namespace internal { 57 58class TraySms::SmsDefaultView : public TrayItemMore { 59 public: 60 explicit SmsDefaultView(TraySms* owner) 61 : TrayItemMore(owner, true) { 62 SetImage(ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 63 IDR_AURA_UBER_TRAY_SMS)); 64 Update(); 65 } 66 67 virtual ~SmsDefaultView() {} 68 69 void Update() { 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)); 73 SetLabel(label); 74 SetAccessibleName(label); 75 } 76 77 private: 78 DISALLOW_COPY_AND_ASSIGN(SmsDefaultView); 79}; 80 81// An entry (row) in SmsDetailedView or NotificationView. 82class TraySms::SmsMessageView : public views::View, 83 public views::ButtonListener { 84 public: 85 enum ViewType { 86 VIEW_DETAILED, 87 VIEW_NOTIFICATION 88 }; 89 90 SmsMessageView(TraySms* owner, 91 ViewType view_type, 92 size_t index, 93 const std::string& number, 94 const std::string& message) 95 : owner_(owner), 96 index_(index) { 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 virtual ~SmsMessageView() { 115 } 116 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); 122 } 123 124 private: 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_UBER_TRAY_SMS_DISMISS)); 131 const int msg_width = owner_->system_tray()->GetSystemBubble()-> 132 bubble_view()->GetPreferredSize().width() - 133 (kNotificationIconWidth + kTrayPopupPaddingHorizontal * 2); 134 message_label_->SizeToFit(msg_width); 135 136 views::GridLayout* layout = new views::GridLayout(this); 137 SetLayoutManager(layout); 138 139 views::ColumnSet* columns = layout->AddColumnSet(0); 140 141 // Message 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); 146 147 // Close button 148 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, 149 0, /* resize percent */ 150 views::GridLayout::FIXED, 151 kNotificationIconWidth, kNotificationIconWidth); 152 153 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_); 160 161 layout->AddPaddingRow(0, kPaddingVertical); 162 } 163 164 void LayoutNotificationView() { 165 SetLayoutManager( 166 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1)); 167 AddChildView(number_label_); 168 message_label_->SizeToFit(kTrayNotificationContentsWidth); 169 AddChildView(message_label_); 170 } 171 172 TraySms* owner_; 173 size_t index_; 174 views::Label* number_label_; 175 views::Label* message_label_; 176 177 DISALLOW_COPY_AND_ASSIGN(SmsMessageView); 178}; 179 180class TraySms::SmsDetailedView : public TrayDetailsView, 181 public ViewClickListener { 182 public: 183 explicit SmsDetailedView(TraySms* owner) 184 : TrayDetailsView(owner) { 185 Init(); 186 Update(); 187 } 188 189 virtual ~SmsDetailedView() { 190 } 191 192 void Init() { 193 CreateScrollableList(); 194 CreateSpecialRow(IDS_ASH_STATUS_TRAY_SMS, this); 195 } 196 197 void Update() { 198 UpdateMessageList(); 199 Layout(); 200 SchedulePaint(); 201 } 202 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; 209 } 210 211 private: 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; 220 continue; 221 } 222 std::string number, text; 223 if (!GetMessageFromDictionary(message, &number, &text)) { 224 LOG(ERROR) << "Error parsing SMS message"; 225 continue; 226 } 227 SmsMessageView* msgview = new SmsMessageView( 228 static_cast<TraySms*>(owner()), SmsMessageView::VIEW_DETAILED, index, 229 number, text); 230 scroll_content()->AddChildView(msgview); 231 } 232 scroller()->Layout(); 233 } 234 235 // Overridden from ViewClickListener. 236 virtual void OnViewClicked(views::View* sender) OVERRIDE { 237 if (sender == footer()->content()) 238 TransitionToDefaultView(); 239 } 240 241 DISALLOW_COPY_AND_ASSIGN(SmsDetailedView); 242}; 243 244class TraySms::SmsNotificationView : public TrayNotificationView { 245 public: 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); 255 } 256 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); 264 } 265 266 // Overridden from TrayNotificationView: 267 virtual void OnClose() OVERRIDE { 268 tray_sms()->RemoveMessage(message_index_); 269 } 270 271 virtual void OnClickAction() OVERRIDE { 272 owner()->PopupDetailedView(0, true); 273 } 274 275 private: 276 TraySms* tray_sms() { 277 return static_cast<TraySms*>(owner()); 278 } 279 280 size_t message_index_; 281 282 DISALLOW_COPY_AND_ASSIGN(SmsNotificationView); 283}; 284 285TraySms::TraySms(SystemTray* system_tray) 286 : SystemTrayItem(system_tray), 287 default_(NULL), 288 detailed_(NULL), 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); 295} 296 297TraySms::~TraySms() { 298 if (chromeos::NetworkHandler::IsInitialized()) { 299 chromeos::NetworkHandler::Get()->network_sms_handler()->RemoveObserver( 300 this); 301 } 302} 303 304views::View* TraySms::CreateDefaultView(user::LoginStatus status) { 305 CHECK(default_ == NULL); 306 default_ = new SmsDefaultView(this); 307 default_->SetVisible(!messages_.empty()); 308 return default_; 309} 310 311views::View* TraySms::CreateDetailedView(user::LoginStatus status) { 312 CHECK(detailed_ == NULL); 313 HideNotificationView(); 314 if (messages_.empty()) 315 return NULL; 316 detailed_ = new SmsDetailedView(this); 317 return detailed_; 318} 319 320views::View* TraySms::CreateNotificationView(user::LoginStatus status) { 321 CHECK(notification_ == NULL); 322 if (detailed_) 323 return NULL; 324 size_t index; 325 std::string number, text; 326 if (GetLatestMessage(&index, &number, &text)) 327 notification_ = new SmsNotificationView(this, index, number, text); 328 return notification_; 329} 330 331void TraySms::DestroyDefaultView() { 332 default_ = NULL; 333} 334 335void TraySms::DestroyDetailedView() { 336 detailed_ = NULL; 337} 338 339void TraySms::DestroyNotificationView() { 340 notification_ = NULL; 341} 342 343void TraySms::MessageReceived(const base::DictionaryValue& message) { 344 345 std::string message_text; 346 if (!message.GetStringWithoutPathExpansion( 347 chromeos::NetworkSmsHandler::kTextKey, &message_text)) { 348 NET_LOG_ERROR("SMS message contains no content.", ""); 349 return; 350 } 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.", ""); 356 return; 357 } 358 std::string message_number; 359 if (!message.GetStringWithoutPathExpansion( 360 chromeos::NetworkSmsHandler::kNumberKey, &message_number)) { 361 NET_LOG_DEBUG("SMS contains no number. Ignoring.", ""); 362 return; 363 } 364 365 NET_LOG_DEBUG("Received SMS from: " + message_number + " with text: " + 366 message_text, ""); 367 368 base::DictionaryValue* dict = new base::DictionaryValue(); 369 dict->SetString(kSmsNumberKey, message_number); 370 dict->SetString(kSmsTextKey, message_text); 371 messages_.Append(dict); 372 Update(true); 373} 374 375bool TraySms::GetLatestMessage(size_t* index, 376 std::string* number, 377 std::string* text) { 378 if (messages_.empty()) 379 return false; 380 base::DictionaryValue* message; 381 size_t message_index = messages_.GetSize() - 1; 382 if (!messages_.GetDictionary(message_index, &message)) 383 return false; 384 if (!GetMessageFromDictionary(message, number, text)) 385 return false; 386 *index = message_index; 387 return true; 388} 389 390void TraySms::RemoveMessage(size_t index) { 391 if (index < messages_.GetSize()) 392 messages_.Remove(index, NULL); 393} 394 395void TraySms::Update(bool notify) { 396 if (messages_.empty()) { 397 if (default_) 398 default_->SetVisible(false); 399 if (detailed_) 400 HideDetailedView(); 401 HideNotificationView(); 402 } else { 403 if (default_) { 404 default_->SetVisible(true); 405 default_->Update(); 406 } 407 if (detailed_) 408 detailed_->Update(); 409 if (notification_) { 410 size_t index; 411 std::string number, text; 412 if (GetLatestMessage(&index, &number, &text)) 413 notification_->Update(index, number, text); 414 } else if (notify) { 415 ShowNotificationView(); 416 } 417 } 418} 419 420} // namespace internal 421} // namespace ash 422