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