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