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/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