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 "chrome/browser/notifications/sync_notifier/synced_notification.h" 6 7 #include "base/basictypes.h" 8 #include "base/strings/string_util.h" 9 #include "base/strings/utf_string_conversions.h" 10 #include "base/time/time.h" 11 #include "base/values.h" 12 #include "chrome/browser/browser_process.h" 13 #include "chrome/browser/notifications/notification.h" 14 #include "chrome/browser/notifications/notification_ui_manager.h" 15 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_delegate.h" 16 #include "content/public/browser/browser_thread.h" 17 #include "sync/protocol/sync.pb.h" 18 #include "sync/protocol/synced_notification_specifics.pb.h" 19 #include "ui/gfx/image/image.h" 20 #include "ui/message_center/message_center_util.h" 21 #include "ui/message_center/notification_types.h" 22 23 namespace { 24 const char kExtensionScheme[] = "synced-notification://"; 25 const char kDefaultSyncedNotificationScheme[] = "https:"; 26 27 // The name of our first synced notification service. 28 // TODO(petewil): remove this hardcoding once we have the synced notification 29 // signalling sync data type set up to provide this. 30 // crbug.com/248337 31 const char kFirstSyncedNotificationServiceId[] = "Google+"; 32 33 34 // Today rich notifications only supports two buttons, make sure we don't 35 // try to supply them with more than this number of buttons. 36 const unsigned int kMaxNotificationButtonIndex = 2; 37 38 bool UseRichNotifications() { 39 return message_center::IsRichNotificationEnabled(); 40 } 41 42 // Schema-less specs default badly in windows. If we find one, add the schema 43 // we expect instead of allowing windows specific GURL code to make it default 44 // to "file:". 45 GURL AddDefaultSchemaIfNeeded(std::string& url_spec) { 46 if (StartsWithASCII(url_spec, std::string("//"), false)) 47 return GURL(std::string(kDefaultSyncedNotificationScheme) + url_spec); 48 49 return GURL(url_spec); 50 } 51 52 } // namespace 53 54 namespace notifier { 55 56 COMPILE_ASSERT(static_cast<sync_pb::CoalescedSyncedNotification_ReadState>( 57 SyncedNotification::kUnread) == 58 sync_pb::CoalescedSyncedNotification_ReadState_UNREAD, 59 local_enum_must_match_protobuf_enum); 60 COMPILE_ASSERT(static_cast<sync_pb::CoalescedSyncedNotification_ReadState>( 61 SyncedNotification::kRead) == 62 sync_pb::CoalescedSyncedNotification_ReadState_READ, 63 local_enum_must_match_protobuf_enum); 64 COMPILE_ASSERT(static_cast<sync_pb::CoalescedSyncedNotification_ReadState>( 65 SyncedNotification::kDismissed) == 66 sync_pb::CoalescedSyncedNotification_ReadState_DISMISSED, 67 local_enum_must_match_protobuf_enum); 68 69 SyncedNotification::SyncedNotification(const syncer::SyncData& sync_data) 70 : notification_manager_(NULL), 71 notifier_service_(NULL), 72 profile_(NULL), 73 active_fetcher_count_(0), 74 toast_state_(true) { 75 Update(sync_data); 76 } 77 78 SyncedNotification::~SyncedNotification() {} 79 80 void SyncedNotification::Update(const syncer::SyncData& sync_data) { 81 // TODO(petewil): Add checking that the notification looks valid. 82 specifics_.CopyFrom(sync_data.GetSpecifics().synced_notification()); 83 } 84 85 sync_pb::EntitySpecifics SyncedNotification::GetEntitySpecifics() const { 86 sync_pb::EntitySpecifics entity_specifics; 87 entity_specifics.mutable_synced_notification()->CopyFrom(specifics_); 88 return entity_specifics; 89 } 90 91 // TODO(petewil): The fetch mechanism appears to be returning two bitmaps on the 92 // mac - perhaps one is regular, one is high dpi? If so, ensure we use the high 93 // dpi bitmap when appropriate. 94 void SyncedNotification::OnFetchComplete(const GURL url, 95 const SkBitmap* bitmap) { 96 // TODO(petewil): Add timeout mechanism in case bitmaps take too long. Do we 97 // already have one built into URLFetcher? 98 // Make sure we are on the thread we expect. 99 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 100 101 // Match the incoming bitmaps to URLs. In case this is a dup, make sure to 102 // try all potentially matching urls. 103 if (GetAppIconUrl() == url && bitmap != NULL) { 104 app_icon_bitmap_ = gfx::Image::CreateFrom1xBitmap(*bitmap); 105 } 106 if (GetImageUrl() == url && bitmap != NULL) { 107 image_bitmap_ = gfx::Image::CreateFrom1xBitmap(*bitmap); 108 } 109 if (GetProfilePictureUrl(0) == url && bitmap != NULL) { 110 sender_bitmap_ = gfx::Image::CreateFrom1xBitmap(*bitmap); 111 } 112 113 // If this URL matches one or more button bitmaps, save them off. 114 for (unsigned int i = 0; i < GetButtonCount(); ++i) { 115 if (GetButtonIconUrl(i) == url && bitmap != NULL) 116 button_bitmaps_[i] = gfx::Image::CreateFrom1xBitmap(*bitmap); 117 } 118 119 // Count off the bitmaps as they arrive. 120 --active_fetcher_count_; 121 122 DVLOG(2) << __FUNCTION__ << " popping bitmap " << url; 123 DVLOG(2) << __FUNCTION__ << " size is " << bitmap->getSize(); 124 125 // Check to see if all images we need are now present. 126 bool app_icon_ready = GetAppIconUrl().is_empty() || 127 !app_icon_bitmap_.IsEmpty(); 128 bool images_ready = GetImageUrl().is_empty() || !image_bitmap_.IsEmpty(); 129 bool sender_picture_ready = GetProfilePictureUrl(0).is_empty() || 130 !sender_bitmap_.IsEmpty(); 131 bool button_bitmaps_ready = true; 132 for (unsigned int j = 0; j < GetButtonCount(); ++j) { 133 if (!GetButtonIconUrl(j).is_empty() && button_bitmaps_[j].IsEmpty()) { 134 button_bitmaps_ready = false; 135 break; 136 } 137 } 138 // See if all bitmaps are accounted for, if so call Show. 139 if (app_icon_ready && images_ready && sender_picture_ready && 140 button_bitmaps_ready) { 141 Show(notification_manager_, notifier_service_, profile_); 142 } 143 } 144 145 void SyncedNotification::QueueBitmapFetchJobs( 146 NotificationUIManager* notification_manager, 147 ChromeNotifierService* notifier_service, 148 Profile* profile) { 149 // If we are not using the MessageCenter, call show now, and the existing 150 // code will handle the bitmap fetch for us. 151 if (!UseRichNotifications()) { 152 Show(notification_manager, notifier_service, profile); 153 return; 154 } 155 156 // Save off the arguments for the call to Show. 157 notification_manager_ = notification_manager; 158 notifier_service_ = notifier_service; 159 profile_ = profile; 160 DCHECK_EQ(active_fetcher_count_, 0); 161 162 // Ensure our bitmap vector has as many entries as there are buttons, 163 // so that when the bitmaps arrive the vector has a slot for them. 164 for (unsigned int i = 0; i < GetButtonCount(); ++i) { 165 button_bitmaps_.push_back(gfx::Image()); 166 AddBitmapToFetchQueue(GetButtonIconUrl(i)); 167 } 168 169 // If there is a profile image bitmap, fetch it 170 if (GetProfilePictureCount() > 0) { 171 // TODO(petewil): When we have the capacity to display more than one bitmap, 172 // modify this code to fetch as many as we can display 173 AddBitmapToFetchQueue(GetProfilePictureUrl(0)); 174 } 175 176 // If the URL is non-empty, add it to our queue of URLs to fetch. 177 AddBitmapToFetchQueue(GetAppIconUrl()); 178 AddBitmapToFetchQueue(GetImageUrl()); 179 180 // If there are no bitmaps, call show now. 181 if (active_fetcher_count_ == 0) { 182 Show(notification_manager, notifier_service, profile); 183 } 184 } 185 186 void SyncedNotification::StartBitmapFetch() { 187 // Now that we have queued and counted them all, start the fetching. 188 ScopedVector<NotificationBitmapFetcher>::iterator iter; 189 for (iter = fetchers_.begin(); iter != fetchers_.end(); ++iter) { 190 (*iter)->Start(profile_); 191 } 192 } 193 194 void SyncedNotification::AddBitmapToFetchQueue(const GURL& url) { 195 // Check for dups, ignore any request for a dup. 196 ScopedVector<NotificationBitmapFetcher>::iterator iter; 197 for (iter = fetchers_.begin(); iter != fetchers_.end(); ++iter) { 198 if ((*iter)->url() == url) 199 return; 200 } 201 202 if (url.is_valid()) { 203 ++active_fetcher_count_; 204 fetchers_.push_back(new NotificationBitmapFetcher(url, this)); 205 DVLOG(2) << __FUNCTION__ << "Pushing bitmap " << url; 206 } 207 } 208 209 void SyncedNotification::Show(NotificationUIManager* notification_manager, 210 ChromeNotifierService* notifier_service, 211 Profile* profile) { 212 // Let NotificationUIManager know that the notification has been dismissed. 213 if (SyncedNotification::kRead == GetReadState() || 214 SyncedNotification::kDismissed == GetReadState() ) { 215 notification_manager->CancelById(GetKey()); 216 DVLOG(2) << "Dismissed or read notification arrived" 217 << GetHeading() << " " << GetText(); 218 return; 219 } 220 221 // Set up the fields we need to send and create a Notification object. 222 GURL image_url = GetImageUrl(); 223 base::string16 text = UTF8ToUTF16(GetText()); 224 base::string16 heading = UTF8ToUTF16(GetHeading()); 225 base::string16 description = UTF8ToUTF16(GetDescription()); 226 base::string16 annotation = UTF8ToUTF16(GetAnnotation()); 227 // TODO(petewil): Eventually put the display name of the sending service here. 228 base::string16 display_source = UTF8ToUTF16(GetAppId()); 229 base::string16 replace_key = UTF8ToUTF16(GetKey()); 230 base::string16 notification_heading = heading; 231 base::string16 notification_text = description; 232 base::string16 newline = UTF8ToUTF16("\n"); 233 234 // The delegate will eventually catch calls that the notification 235 // was read or deleted, and send the changes back to the server. 236 scoped_refptr<NotificationDelegate> delegate = 237 new ChromeNotifierDelegate(GetKey(), notifier_service); 238 239 // Some inputs and fields are only used if there is a notification center. 240 if (UseRichNotifications()) { 241 base::Time creation_time = 242 base::Time::FromDoubleT(static_cast<double>(GetCreationTime())); 243 int priority = GetPriority(); 244 unsigned int button_count = GetButtonCount(); 245 246 // Deduce which notification template to use from the data. 247 message_center::NotificationType notification_type = 248 message_center::NOTIFICATION_TYPE_BASE_FORMAT; 249 if (!image_url.is_empty()) { 250 notification_type = message_center::NOTIFICATION_TYPE_IMAGE; 251 } else if (button_count > 0) { 252 notification_type = message_center::NOTIFICATION_TYPE_BASE_FORMAT; 253 } 254 255 // Fill the optional fields with the information we need to make a 256 // notification. 257 message_center::RichNotificationData rich_notification_data; 258 rich_notification_data.timestamp = creation_time; 259 if (priority != SyncedNotification::kUndefinedPriority) 260 rich_notification_data.priority = priority; 261 262 // Fill in the button data. 263 // TODO(petewil): Today Rich notifiations are limited to two buttons. 264 // When rich notifications supports more, remove the 265 // "&& i < kMaxNotificationButtonIndex" clause below. 266 for (unsigned int i = 0; 267 i < button_count 268 && i < button_bitmaps_.size() 269 && i < kMaxNotificationButtonIndex; 270 ++i) { 271 // Stop at the first button with no title 272 std::string title = GetButtonTitle(i); 273 if (title.empty()) 274 break; 275 message_center::ButtonInfo button_info(UTF8ToUTF16(title)); 276 if (!button_bitmaps_[i].IsEmpty()) 277 button_info.icon = button_bitmaps_[i]; 278 rich_notification_data.buttons.push_back(button_info); 279 } 280 281 // Fill in the bitmap images. 282 if (!image_bitmap_.IsEmpty()) 283 rich_notification_data.image = image_bitmap_; 284 285 // Set the ContextMessage inside the rich notification data for the 286 // annotation. 287 rich_notification_data.context_message = annotation; 288 289 // If there is at least one person sending, use the first picture. 290 // TODO(petewil): Someday combine multiple profile photos here. 291 gfx::Image icon_bitmap = app_icon_bitmap_; 292 if (GetProfilePictureCount() >= 1) { 293 icon_bitmap = sender_bitmap_; 294 } 295 296 Notification ui_notification(notification_type, 297 GetOriginUrl(), 298 notification_heading, 299 notification_text, 300 icon_bitmap, 301 blink::WebTextDirectionDefault, 302 message_center::NotifierId(GetOriginUrl()), 303 display_source, 304 replace_key, 305 rich_notification_data, 306 delegate.get()); 307 308 // In case the notification is not supposed to be toasted, pretend that it 309 // has already been shown. 310 ui_notification.set_shown_as_popup(!toast_state_); 311 312 notification_manager->Add(ui_notification, profile); 313 } else { 314 // In this case we have a Webkit Notification, not a Rich Notification. 315 Notification ui_notification(GetOriginUrl(), 316 GetAppIconUrl(), 317 notification_heading, 318 notification_text, 319 blink::WebTextDirectionDefault, 320 display_source, 321 replace_key, 322 delegate.get()); 323 324 notification_manager->Add(ui_notification, profile); 325 } 326 327 DVLOG(1) << "Showing Synced Notification! " << heading << " " << text 328 << " " << GetAppIconUrl() << " " << replace_key << " " 329 << GetProfilePictureUrl(0) << " " << GetReadState(); 330 331 return; 332 } 333 334 // This should detect even small changes in case the server updated the 335 // notification. We ignore the timestamp if other fields match. 336 bool SyncedNotification::EqualsIgnoringReadState( 337 const SyncedNotification& other) const { 338 if (GetTitle() == other.GetTitle() && 339 GetHeading() == other.GetHeading() && 340 GetDescription() == other.GetDescription() && 341 GetAnnotation() == other.GetAnnotation() && 342 GetAppId() == other.GetAppId() && 343 GetKey() == other.GetKey() && 344 GetOriginUrl() == other.GetOriginUrl() && 345 GetAppIconUrl() == other.GetAppIconUrl() && 346 GetImageUrl() == other.GetImageUrl() && 347 GetText() == other.GetText() && 348 // We intentionally skip read state 349 GetCreationTime() == other.GetCreationTime() && 350 GetPriority() == other.GetPriority() && 351 GetDefaultDestinationTitle() == other.GetDefaultDestinationTitle() && 352 GetDefaultDestinationIconUrl() == other.GetDefaultDestinationIconUrl() && 353 GetNotificationCount() == other.GetNotificationCount() && 354 GetButtonCount() == other.GetButtonCount() && 355 GetProfilePictureCount() == other.GetProfilePictureCount()) { 356 357 // If all the surface data matched, check, to see if contained data also 358 // matches, titles and messages. 359 size_t count = GetNotificationCount(); 360 for (size_t ii = 0; ii < count; ++ii) { 361 if (GetContainedNotificationTitle(ii) != 362 other.GetContainedNotificationTitle(ii)) 363 return false; 364 if (GetContainedNotificationMessage(ii) != 365 other.GetContainedNotificationMessage(ii)) 366 return false; 367 } 368 369 // Make sure buttons match. 370 count = GetButtonCount(); 371 for (size_t jj = 0; jj < count; ++jj) { 372 if (GetButtonTitle(jj) != other.GetButtonTitle(jj)) 373 return false; 374 if (GetButtonIconUrl(jj) != other.GetButtonIconUrl(jj)) 375 return false; 376 } 377 378 // Make sure profile icons match 379 count = GetButtonCount(); 380 for (size_t kk = 0; kk < count; ++kk) { 381 if (GetProfilePictureUrl(kk) != other.GetProfilePictureUrl(kk)) 382 return false; 383 } 384 385 // If buttons and notifications matched, they are equivalent. 386 return true; 387 } 388 389 return false; 390 } 391 392 void SyncedNotification::LogNotification() { 393 std::string readStateString("Unread"); 394 if (SyncedNotification::kRead == GetReadState()) 395 readStateString = "Read"; 396 else if (SyncedNotification::kDismissed == GetReadState()) 397 readStateString = "Dismissed"; 398 399 DVLOG(2) << " Notification: Heading is " << GetHeading() 400 << " description is " << GetDescription() 401 << " key is " << GetKey() 402 << " read state is " << readStateString; 403 } 404 405 // Set the read state on the notification, returns true for success. 406 void SyncedNotification::SetReadState(const ReadState& read_state) { 407 408 // Convert the read state to the protobuf type for read state. 409 if (kDismissed == read_state) 410 specifics_.mutable_coalesced_notification()->set_read_state( 411 sync_pb::CoalescedSyncedNotification_ReadState_DISMISSED); 412 else if (kUnread == read_state) 413 specifics_.mutable_coalesced_notification()->set_read_state( 414 sync_pb::CoalescedSyncedNotification_ReadState_UNREAD); 415 else if (kRead == read_state) 416 specifics_.mutable_coalesced_notification()->set_read_state( 417 sync_pb::CoalescedSyncedNotification_ReadState_READ); 418 else 419 NOTREACHED(); 420 } 421 422 void SyncedNotification::NotificationHasBeenRead() { 423 SetReadState(kRead); 424 } 425 426 void SyncedNotification::NotificationHasBeenDismissed() { 427 SetReadState(kDismissed); 428 } 429 430 std::string SyncedNotification::GetTitle() const { 431 if (!specifics_.coalesced_notification().render_info().expanded_info(). 432 simple_expanded_layout().has_title()) 433 return std::string(); 434 435 return specifics_.coalesced_notification().render_info().expanded_info(). 436 simple_expanded_layout().title(); 437 } 438 439 std::string SyncedNotification::GetHeading() const { 440 if (!specifics_.coalesced_notification().render_info().collapsed_info(). 441 simple_collapsed_layout().has_heading()) 442 return std::string(); 443 444 return specifics_.coalesced_notification().render_info().collapsed_info(). 445 simple_collapsed_layout().heading(); 446 } 447 448 std::string SyncedNotification::GetDescription() const { 449 if (!specifics_.coalesced_notification().render_info().collapsed_info(). 450 simple_collapsed_layout().has_description()) 451 return std::string(); 452 453 return specifics_.coalesced_notification().render_info().collapsed_info(). 454 simple_collapsed_layout().description(); 455 } 456 457 std::string SyncedNotification::GetAnnotation() const { 458 if (!specifics_.coalesced_notification().render_info().collapsed_info(). 459 simple_collapsed_layout().has_annotation()) 460 return std::string(); 461 462 return specifics_.coalesced_notification().render_info().collapsed_info(). 463 simple_collapsed_layout().annotation(); 464 } 465 466 std::string SyncedNotification::GetAppId() const { 467 if (!specifics_.coalesced_notification().has_app_id()) 468 return std::string(); 469 return specifics_.coalesced_notification().app_id(); 470 } 471 472 std::string SyncedNotification::GetKey() const { 473 if (!specifics_.coalesced_notification().has_key()) 474 return std::string(); 475 return specifics_.coalesced_notification().key(); 476 } 477 478 GURL SyncedNotification::GetOriginUrl() const { 479 std::string origin_url(kExtensionScheme); 480 origin_url += GetAppId(); 481 return GURL(origin_url); 482 } 483 484 GURL SyncedNotification::GetAppIconUrl() const { 485 if (!specifics_.coalesced_notification().render_info().collapsed_info(). 486 simple_collapsed_layout().has_app_icon()) 487 return GURL(); 488 489 std::string url_spec = specifics_.coalesced_notification().render_info(). 490 collapsed_info().simple_collapsed_layout().app_icon().url(); 491 492 return AddDefaultSchemaIfNeeded(url_spec); 493 } 494 495 // TODO(petewil): This ignores all but the first image. If Rich Notifications 496 // supports more images someday, then fetch all images. 497 GURL SyncedNotification::GetImageUrl() const { 498 if (specifics_.coalesced_notification().render_info().collapsed_info(). 499 simple_collapsed_layout().media_size() == 0) 500 return GURL(); 501 502 if (!specifics_.coalesced_notification().render_info().collapsed_info(). 503 simple_collapsed_layout().media(0).image().has_url()) 504 return GURL(); 505 506 std::string url_spec = specifics_.coalesced_notification().render_info(). 507 collapsed_info().simple_collapsed_layout().media(0).image().url(); 508 509 return AddDefaultSchemaIfNeeded(url_spec); 510 } 511 512 std::string SyncedNotification::GetText() const { 513 if (!specifics_.coalesced_notification().render_info().expanded_info(). 514 simple_expanded_layout().has_text()) 515 return std::string(); 516 517 return specifics_.coalesced_notification().render_info().expanded_info(). 518 simple_expanded_layout().text(); 519 } 520 521 SyncedNotification::ReadState SyncedNotification::GetReadState() const { 522 DCHECK(specifics_.coalesced_notification().has_read_state()); 523 524 sync_pb::CoalescedSyncedNotification_ReadState found_read_state = 525 specifics_.coalesced_notification().read_state(); 526 527 if (found_read_state == 528 sync_pb::CoalescedSyncedNotification_ReadState_DISMISSED) { 529 return kDismissed; 530 } else if (found_read_state == 531 sync_pb::CoalescedSyncedNotification_ReadState_UNREAD) { 532 return kUnread; 533 } else if (found_read_state == 534 sync_pb::CoalescedSyncedNotification_ReadState_READ) { 535 return kRead; 536 } else { 537 NOTREACHED(); 538 return static_cast<SyncedNotification::ReadState>(found_read_state); 539 } 540 } 541 542 // Time in milliseconds since the unix epoch, or 0 if not available. 543 uint64 SyncedNotification::GetCreationTime() const { 544 if (!specifics_.coalesced_notification().has_creation_time_msec()) 545 return 0; 546 547 return specifics_.coalesced_notification().creation_time_msec(); 548 } 549 550 int SyncedNotification::GetPriority() const { 551 if (!specifics_.coalesced_notification().has_priority()) 552 return kUndefinedPriority; 553 int protobuf_priority = specifics_.coalesced_notification().priority(); 554 555 // Convert the prioroty to the scheme used by the notification center. 556 if (protobuf_priority == 557 sync_pb::CoalescedSyncedNotification_Priority_LOW) { 558 return message_center::LOW_PRIORITY; 559 } else if (protobuf_priority == 560 sync_pb::CoalescedSyncedNotification_Priority_STANDARD) { 561 return message_center::DEFAULT_PRIORITY; 562 } else if (protobuf_priority == 563 sync_pb::CoalescedSyncedNotification_Priority_HIGH) { 564 return message_center::HIGH_PRIORITY; 565 } else { 566 // Complain if this is a new priority we have not seen before. 567 DCHECK(protobuf_priority < 568 sync_pb::CoalescedSyncedNotification_Priority_LOW || 569 sync_pb::CoalescedSyncedNotification_Priority_HIGH < 570 protobuf_priority); 571 return kUndefinedPriority; 572 } 573 } 574 575 size_t SyncedNotification::GetNotificationCount() const { 576 return specifics_.coalesced_notification().render_info(). 577 expanded_info().collapsed_info_size(); 578 } 579 580 size_t SyncedNotification::GetButtonCount() const { 581 return specifics_.coalesced_notification().render_info().collapsed_info(). 582 target_size(); 583 } 584 585 size_t SyncedNotification::GetProfilePictureCount() const { 586 return specifics_.coalesced_notification().render_info().collapsed_info(). 587 simple_collapsed_layout().profile_image_size(); 588 } 589 590 GURL SyncedNotification::GetProfilePictureUrl(unsigned int which_url) const { 591 if (GetProfilePictureCount() <= which_url) 592 return GURL(); 593 594 std::string url_spec = specifics_.coalesced_notification().render_info(). 595 collapsed_info().simple_collapsed_layout().profile_image(which_url). 596 image_url(); 597 598 return AddDefaultSchemaIfNeeded(url_spec); 599 } 600 601 602 std::string SyncedNotification::GetDefaultDestinationTitle() const { 603 if (!specifics_.coalesced_notification().render_info().collapsed_info(). 604 default_destination().icon().has_alt_text()) { 605 return std::string(); 606 } 607 return specifics_.coalesced_notification().render_info().collapsed_info(). 608 default_destination().icon().alt_text(); 609 } 610 611 GURL SyncedNotification::GetDefaultDestinationIconUrl() const { 612 if (!specifics_.coalesced_notification().render_info().collapsed_info(). 613 default_destination().icon().has_url()) { 614 return GURL(); 615 } 616 std::string url_spec = specifics_.coalesced_notification().render_info(). 617 collapsed_info().default_destination().icon().url(); 618 619 return AddDefaultSchemaIfNeeded(url_spec); 620 } 621 622 GURL SyncedNotification::GetDefaultDestinationUrl() const { 623 if (!specifics_.coalesced_notification().render_info().collapsed_info(). 624 default_destination().has_url()) { 625 return GURL(); 626 } 627 std::string url_spec = specifics_.coalesced_notification().render_info(). 628 collapsed_info().default_destination().url(); 629 630 return AddDefaultSchemaIfNeeded(url_spec); 631 } 632 633 std::string SyncedNotification::GetButtonTitle( 634 unsigned int which_button) const { 635 // Must ensure that we have a target before trying to access it. 636 if (GetButtonCount() <= which_button) 637 return std::string(); 638 if (!specifics_.coalesced_notification().render_info().collapsed_info(). 639 target(which_button).action().icon().has_alt_text()) { 640 return std::string(); 641 } 642 return specifics_.coalesced_notification().render_info().collapsed_info(). 643 target(which_button).action().icon().alt_text(); 644 } 645 646 GURL SyncedNotification::GetButtonIconUrl(unsigned int which_button) const { 647 // Must ensure that we have a target before trying to access it. 648 if (GetButtonCount() <= which_button) 649 return GURL(); 650 if (!specifics_.coalesced_notification().render_info().collapsed_info(). 651 target(which_button).action().icon().has_url()) { 652 return GURL(); 653 } 654 std::string url_spec = specifics_.coalesced_notification().render_info(). 655 collapsed_info().target(which_button).action().icon().url(); 656 657 return AddDefaultSchemaIfNeeded(url_spec); 658 } 659 660 GURL SyncedNotification::GetButtonUrl(unsigned int which_button) const { 661 // Must ensure that we have a target before trying to access it. 662 if (GetButtonCount() <= which_button) 663 return GURL(); 664 if (!specifics_.coalesced_notification().render_info().collapsed_info(). 665 target(which_button).action().has_url()) { 666 return GURL(); 667 } 668 std::string url_spec = specifics_.coalesced_notification().render_info(). 669 collapsed_info().target(which_button).action().url(); 670 671 return AddDefaultSchemaIfNeeded(url_spec); 672 } 673 674 std::string SyncedNotification::GetContainedNotificationTitle( 675 int index) const { 676 if (specifics_.coalesced_notification().render_info().expanded_info(). 677 collapsed_info_size() < index + 1) 678 return std::string(); 679 680 return specifics_.coalesced_notification().render_info().expanded_info(). 681 collapsed_info(index).simple_collapsed_layout().heading(); 682 } 683 684 std::string SyncedNotification::GetContainedNotificationMessage( 685 int index) const { 686 if (specifics_.coalesced_notification().render_info().expanded_info(). 687 collapsed_info_size() < index + 1) 688 return std::string(); 689 690 return specifics_.coalesced_notification().render_info().expanded_info(). 691 collapsed_info(index).simple_collapsed_layout().description(); 692 } 693 694 std::string SyncedNotification::GetSendingServiceId() const { 695 // TODO(petewil): We are building a new protocol (a new sync datatype) to send 696 // the service name and icon from the server. For now this method is 697 // hardcoded to the name of our first service using synced notifications. 698 // Once the new protocol is built, remove this hardcoding. 699 return kFirstSyncedNotificationServiceId; 700 } 701 702 void SyncedNotification::SetToastState(bool toast_state) { 703 toast_state_ = toast_state; 704 } 705 706 } // namespace notifier 707