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