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