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