Home | History | Annotate | Download | only in sync_notifier
      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