OLD | NEW |
| (Empty) |
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/extensions/app_notification_manager.h" | |
6 | |
7 #include "base/auto_reset.h" | |
8 #include "base/bind.h" | |
9 #include "base/files/file_path.h" | |
10 #include "base/location.h" | |
11 #include "base/metrics/histogram.h" | |
12 #include "base/perftimer.h" | |
13 #include "base/stl_util.h" | |
14 #include "base/time.h" | |
15 #include "chrome/browser/profiles/profile.h" | |
16 #include "chrome/common/chrome_notification_types.h" | |
17 #include "chrome/common/extensions/extension.h" | |
18 #include "content/public/browser/notification_service.h" | |
19 #include "sync/api/sync_error_factory.h" | |
20 #include "sync/protocol/app_notification_specifics.pb.h" | |
21 #include "sync/protocol/sync.pb.h" | |
22 | |
23 using content::BrowserThread; | |
24 | |
25 typedef std::map<std::string, syncer::SyncData> SyncDataMap; | |
26 | |
27 namespace extensions { | |
28 | |
29 namespace { | |
30 | |
31 class GuidComparator | |
32 : public std::binary_function<linked_ptr<AppNotification>, | |
33 std::string, | |
34 bool> { | |
35 public: | |
36 bool operator() (linked_ptr<AppNotification> notif, | |
37 const std::string& guid) const { | |
38 return notif->guid() == guid; | |
39 } | |
40 }; | |
41 | |
42 const AppNotification* FindByGuid(const AppNotificationList& list, | |
43 const std::string& guid) { | |
44 AppNotificationList::const_iterator iter = std::find_if( | |
45 list.begin(), list.end(), std::bind2nd(GuidComparator(), guid)); | |
46 return iter == list.end() ? NULL : iter->get(); | |
47 } | |
48 | |
49 void RemoveByGuid(AppNotificationList* list, const std::string& guid) { | |
50 if (!list) | |
51 return; | |
52 | |
53 AppNotificationList::iterator iter = std::find_if( | |
54 list->begin(), list->end(), std::bind2nd(GuidComparator(), guid)); | |
55 if (iter != list->end()) | |
56 list->erase(iter); | |
57 } | |
58 | |
59 void PopulateGuidToSyncDataMap(const syncer::SyncDataList& sync_data, | |
60 SyncDataMap* data_map) { | |
61 for (syncer::SyncDataList::const_iterator iter = sync_data.begin(); | |
62 iter != sync_data.end(); ++iter) { | |
63 (*data_map)[iter->GetSpecifics().app_notification().guid()] = *iter; | |
64 } | |
65 } | |
66 } // namespace | |
67 | |
68 const unsigned int AppNotificationManager::kMaxNotificationPerApp = 5; | |
69 | |
70 AppNotificationManager::AppNotificationManager(Profile* profile) | |
71 : profile_(profile), | |
72 models_associated_(false), | |
73 processing_syncer_changes_(false) { | |
74 registrar_.Add(this, | |
75 chrome::NOTIFICATION_EXTENSION_UNINSTALLED, | |
76 content::Source<Profile>(profile_)); | |
77 } | |
78 | |
79 void AppNotificationManager::Init() { | |
80 base::FilePath storage_path = | |
81 profile_->GetPath().AppendASCII("App Notifications"); | |
82 load_timer_.reset(new PerfTimer()); | |
83 BrowserThread::PostTask( | |
84 BrowserThread::FILE, | |
85 FROM_HERE, | |
86 base::Bind(&AppNotificationManager::LoadOnFileThread, | |
87 this, storage_path)); | |
88 } | |
89 | |
90 bool AppNotificationSortPredicate(const linked_ptr<AppNotification> a1, | |
91 const linked_ptr<AppNotification> a2) { | |
92 return a1.get()->creation_time() < a2.get()->creation_time(); | |
93 } | |
94 | |
95 bool AppNotificationManager::Add(AppNotification* item) { | |
96 // Do this first since we own the incoming item and hence want to delete | |
97 // it in error paths. | |
98 linked_ptr<AppNotification> linked_item(item); | |
99 if (!loaded()) | |
100 return false; | |
101 const std::string& extension_id = item->extension_id(); | |
102 AppNotificationList& list = GetAllInternal(extension_id); | |
103 list.push_back(linked_item); | |
104 | |
105 SyncAddChange(*linked_item); | |
106 | |
107 std::sort(list.begin(), list.end(), AppNotificationSortPredicate); | |
108 | |
109 if (list.size() > AppNotificationManager::kMaxNotificationPerApp) { | |
110 AppNotification* removed = list.begin()->get(); | |
111 SyncRemoveChange(*removed); | |
112 list.erase(list.begin()); | |
113 } | |
114 | |
115 if (storage_.get()) { | |
116 BrowserThread::PostTask( | |
117 BrowserThread::FILE, | |
118 FROM_HERE, | |
119 base::Bind(&AppNotificationManager::SaveOnFileThread, | |
120 this, extension_id, CopyAppNotificationList(list))); | |
121 } | |
122 | |
123 content::NotificationService::current()->Notify( | |
124 chrome::NOTIFICATION_APP_NOTIFICATION_STATE_CHANGED, | |
125 content::Source<Profile>(profile_), | |
126 content::Details<const std::string>(&extension_id)); | |
127 | |
128 return true; | |
129 } | |
130 | |
131 const AppNotificationList* AppNotificationManager::GetAll( | |
132 const std::string& extension_id) const { | |
133 if (!loaded()) | |
134 return NULL; | |
135 if (ContainsKey(*notifications_, extension_id)) | |
136 return &((*notifications_)[extension_id]); | |
137 return NULL; | |
138 } | |
139 | |
140 const AppNotification* AppNotificationManager::GetLast( | |
141 const std::string& extension_id) { | |
142 if (!loaded()) | |
143 return NULL; | |
144 NotificationMap::iterator found = notifications_->find(extension_id); | |
145 if (found == notifications_->end()) | |
146 return NULL; | |
147 const AppNotificationList& list = found->second; | |
148 if (list.empty()) | |
149 return NULL; | |
150 return list.rbegin()->get(); | |
151 } | |
152 | |
153 void AppNotificationManager::ClearAll(const std::string& extension_id) { | |
154 if (!loaded()) | |
155 return; | |
156 NotificationMap::iterator found = notifications_->find(extension_id); | |
157 if (found != notifications_->end()) { | |
158 SyncClearAllChange(found->second); | |
159 notifications_->erase(found); | |
160 } | |
161 | |
162 if (storage_.get()) { | |
163 BrowserThread::PostTask( | |
164 BrowserThread::FILE, | |
165 FROM_HERE, | |
166 base::Bind(&AppNotificationManager::DeleteOnFileThread, | |
167 this, extension_id)); | |
168 } | |
169 | |
170 content::NotificationService::current()->Notify( | |
171 chrome::NOTIFICATION_APP_NOTIFICATION_STATE_CHANGED, | |
172 content::Source<Profile>(profile_), | |
173 content::Details<const std::string>(&extension_id)); | |
174 } | |
175 | |
176 void AppNotificationManager::Observe( | |
177 int type, | |
178 const content::NotificationSource& source, | |
179 const content::NotificationDetails& details) { | |
180 CHECK(type == chrome::NOTIFICATION_EXTENSION_UNINSTALLED); | |
181 ClearAll(content::Details<const Extension>(details).ptr()->id()); | |
182 } | |
183 | |
184 syncer::SyncDataList AppNotificationManager::GetAllSyncData( | |
185 syncer::ModelType type) const { | |
186 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
187 DCHECK(loaded()); | |
188 DCHECK_EQ(syncer::APP_NOTIFICATIONS, type); | |
189 syncer::SyncDataList data; | |
190 for (NotificationMap::const_iterator iter = notifications_->begin(); | |
191 iter != notifications_->end(); ++iter) { | |
192 | |
193 // Skip local notifications since they should not be synced. | |
194 const AppNotificationList list = (*iter).second; | |
195 for (AppNotificationList::const_iterator list_iter = list.begin(); | |
196 list_iter != list.end(); ++list_iter) { | |
197 const AppNotification* notification = (*list_iter).get(); | |
198 if (notification->is_local()) { | |
199 continue; | |
200 } | |
201 data.push_back(CreateSyncDataFromNotification(*notification)); | |
202 } | |
203 } | |
204 | |
205 return data; | |
206 } | |
207 | |
208 syncer::SyncError AppNotificationManager::ProcessSyncChanges( | |
209 const tracked_objects::Location& from_here, | |
210 const syncer::SyncChangeList& change_list) { | |
211 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
212 DCHECK(loaded()); | |
213 if (!models_associated_) { | |
214 return sync_error_factory_->CreateAndUploadError( | |
215 FROM_HERE, | |
216 "Models not yet associated."); | |
217 } | |
218 | |
219 base::AutoReset<bool> processing_changes(&processing_syncer_changes_, true); | |
220 | |
221 syncer::SyncError error; | |
222 for (syncer::SyncChangeList::const_iterator iter = change_list.begin(); | |
223 iter != change_list.end(); ++iter) { | |
224 syncer::SyncData sync_data = iter->sync_data(); | |
225 DCHECK_EQ(syncer::APP_NOTIFICATIONS, sync_data.GetDataType()); | |
226 syncer::SyncChange::SyncChangeType change_type = iter->change_type(); | |
227 | |
228 scoped_ptr<AppNotification> new_notif(CreateNotificationFromSyncData( | |
229 sync_data)); | |
230 if (!new_notif.get()) { | |
231 NOTREACHED() << "Failed to read notification."; | |
232 continue; | |
233 } | |
234 const AppNotification* existing_notif = GetNotification( | |
235 new_notif->extension_id(), new_notif->guid()); | |
236 if (existing_notif && existing_notif->is_local()) { | |
237 NOTREACHED() << "Matched with notification marked as local"; | |
238 error = sync_error_factory_->CreateAndUploadError( | |
239 FROM_HERE, | |
240 "ProcessSyncChanges received a local only notification" + | |
241 syncer::SyncChange::ChangeTypeToString(change_type)); | |
242 continue; | |
243 } | |
244 | |
245 switch (change_type) { | |
246 case syncer::SyncChange::ACTION_ADD: | |
247 if (!existing_notif) { | |
248 Add(new_notif.release()); | |
249 } else { | |
250 DLOG(ERROR) << "Got ADD change for an existing item.\n" | |
251 << "Existing item: " << existing_notif->ToString() | |
252 << "\nItem in ADD change: " << new_notif->ToString(); | |
253 } | |
254 break; | |
255 case syncer::SyncChange::ACTION_DELETE: | |
256 if (existing_notif) { | |
257 Remove(new_notif->extension_id(), new_notif->guid()); | |
258 } else { | |
259 // This should never happen. But we are seeting this sometimes, and | |
260 // it stops all of sync. See bug http://crbug.com/108088 | |
261 // So until we figure out the root cause, log an error and ignore. | |
262 DLOG(ERROR) << "Got DELETE change for non-existing item.\n" | |
263 << "Item in DELETE change: " << new_notif->ToString(); | |
264 } | |
265 break; | |
266 case syncer::SyncChange::ACTION_UPDATE: | |
267 // Although app notifications are immutable from the model perspective, | |
268 // sync can send UPDATE changes due to encryption / meta-data changes. | |
269 // So ignore UPDATE changes when the exitsing and new notification | |
270 // objects are the same. Log an error otherwise. | |
271 if (!existing_notif) { | |
272 DLOG(ERROR) << "Got UPDATE change for non-existing item." | |
273 << "Item in UPDATE change: " << new_notif->ToString(); | |
274 } else if (!existing_notif->Equals(*new_notif)) { | |
275 DLOG(ERROR) << "Got invalid UPDATE change:" | |
276 << "New and existing notifications should be the same.\n" | |
277 << "Existing item: " << existing_notif->ToString() << "\n" | |
278 << "Item in UPDATE change: " << new_notif->ToString(); | |
279 } | |
280 break; | |
281 default: | |
282 break; | |
283 } | |
284 } | |
285 | |
286 return error; | |
287 } | |
288 | |
289 syncer::SyncMergeResult AppNotificationManager::MergeDataAndStartSyncing( | |
290 syncer::ModelType type, | |
291 const syncer::SyncDataList& initial_sync_data, | |
292 scoped_ptr<syncer::SyncChangeProcessor> sync_processor, | |
293 scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) { | |
294 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
295 syncer::SyncMergeResult merge_result(type); | |
296 // AppNotificationDataTypeController ensures that modei is fully should before | |
297 // this method is called by waiting until the load notification is received | |
298 // from AppNotificationManager. | |
299 DCHECK(loaded()); | |
300 DCHECK_EQ(type, syncer::APP_NOTIFICATIONS); | |
301 DCHECK(!sync_processor_.get()); | |
302 DCHECK(sync_processor.get()); | |
303 DCHECK(sync_error_factory.get()); | |
304 sync_processor_ = sync_processor.Pass(); | |
305 sync_error_factory_ = sync_error_factory.Pass(); | |
306 | |
307 // We may add, or remove notifications here, so ensure we don't step on | |
308 // our own toes. | |
309 base::AutoReset<bool> processing_changes(&processing_syncer_changes_, true); | |
310 | |
311 SyncDataMap local_data_map; | |
312 PopulateGuidToSyncDataMap(GetAllSyncData(syncer::APP_NOTIFICATIONS), | |
313 &local_data_map); | |
314 | |
315 for (syncer::SyncDataList::const_iterator iter = initial_sync_data.begin(); | |
316 iter != initial_sync_data.end(); ++iter) { | |
317 const syncer::SyncData& sync_data = *iter; | |
318 DCHECK_EQ(syncer::APP_NOTIFICATIONS, sync_data.GetDataType()); | |
319 scoped_ptr<AppNotification> sync_notif(CreateNotificationFromSyncData( | |
320 sync_data)); | |
321 CHECK(sync_notif.get()); | |
322 const AppNotification* local_notif = GetNotification( | |
323 sync_notif->extension_id(), sync_notif->guid()); | |
324 if (local_notif) { | |
325 local_data_map.erase(sync_notif->guid()); | |
326 // Local notification should always match with sync notification as | |
327 // notifications are immutable. | |
328 if (local_notif->is_local() || !sync_notif->Equals(*local_notif)) { | |
329 merge_result.set_error(sync_error_factory_->CreateAndUploadError( | |
330 FROM_HERE, | |
331 "MergeDataAndStartSyncing failed: local notification and sync " | |
332 "notification have same guid but different data.")); | |
333 return merge_result; | |
334 } | |
335 } else { | |
336 // Sync model has a notification that local model does not, add it. | |
337 Add(sync_notif.release()); | |
338 } | |
339 } | |
340 | |
341 // TODO(munjal): crbug.com/10059. Work with Lingesh/Antony to resolve. | |
342 syncer::SyncChangeList new_changes; | |
343 for (SyncDataMap::const_iterator iter = local_data_map.begin(); | |
344 iter != local_data_map.end(); ++iter) { | |
345 new_changes.push_back( | |
346 syncer::SyncChange(FROM_HERE, | |
347 syncer::SyncChange::ACTION_ADD, | |
348 iter->second)); | |
349 } | |
350 | |
351 if (new_changes.size() > 0) { | |
352 merge_result.set_error( | |
353 sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes)); | |
354 } | |
355 models_associated_ = !merge_result.error().IsSet(); | |
356 return merge_result; | |
357 } | |
358 | |
359 void AppNotificationManager::StopSyncing(syncer::ModelType type) { | |
360 DCHECK_EQ(type, syncer::APP_NOTIFICATIONS); | |
361 models_associated_ = false; | |
362 sync_processor_.reset(); | |
363 sync_error_factory_.reset(); | |
364 } | |
365 | |
366 AppNotificationManager::~AppNotificationManager() { | |
367 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
368 // Post a task to delete our storage on the file thread. | |
369 BrowserThread::DeleteSoon(BrowserThread::FILE, | |
370 FROM_HERE, | |
371 storage_.release()); | |
372 } | |
373 | |
374 void AppNotificationManager::LoadOnFileThread( | |
375 const base::FilePath& storage_path) { | |
376 PerfTimer timer; | |
377 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
378 DCHECK(!loaded()); | |
379 | |
380 storage_.reset(AppNotificationStorage::Create(storage_path)); | |
381 if (!storage_.get()) | |
382 return; | |
383 scoped_ptr<NotificationMap> result(new NotificationMap()); | |
384 std::set<std::string> ids; | |
385 if (!storage_->GetExtensionIds(&ids)) | |
386 return; | |
387 std::set<std::string>::const_iterator i; | |
388 for (i = ids.begin(); i != ids.end(); ++i) { | |
389 const std::string& id = *i; | |
390 AppNotificationList& list = (*result)[id]; | |
391 if (!storage_->Get(id, &list)) | |
392 result->erase(id); | |
393 } | |
394 | |
395 BrowserThread::PostTask( | |
396 BrowserThread::UI, | |
397 FROM_HERE, | |
398 base::Bind(&AppNotificationManager::HandleLoadResults, | |
399 this, result.release())); | |
400 | |
401 UMA_HISTOGRAM_LONG_TIMES("AppNotification.MgrFileThreadLoadTime", | |
402 timer.Elapsed()); | |
403 } | |
404 | |
405 void AppNotificationManager::HandleLoadResults(NotificationMap* map) { | |
406 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
407 DCHECK(map); | |
408 DCHECK(!loaded()); | |
409 notifications_.reset(map); | |
410 UMA_HISTOGRAM_LONG_TIMES("AppNotification.MgrLoadDelay", | |
411 load_timer_->Elapsed()); | |
412 load_timer_.reset(); | |
413 | |
414 // Generate STATE_CHANGED notifications for extensions that have at | |
415 // least one notification loaded. | |
416 int app_count = 0; | |
417 int notification_count = 0; | |
418 NotificationMap::const_iterator i; | |
419 for (i = map->begin(); i != map->end(); ++i) { | |
420 const std::string& id = i->first; | |
421 if (i->second.empty()) | |
422 continue; | |
423 app_count++; | |
424 notification_count += i->second.size(); | |
425 content::NotificationService::current()->Notify( | |
426 chrome::NOTIFICATION_APP_NOTIFICATION_STATE_CHANGED, | |
427 content::Source<Profile>(profile_), | |
428 content::Details<const std::string>(&id)); | |
429 } | |
430 UMA_HISTOGRAM_COUNTS("AppNotification.MgrLoadAppCount", app_count); | |
431 UMA_HISTOGRAM_COUNTS("AppNotification.MgrLoadTotalCount", | |
432 notification_count); | |
433 | |
434 // Generate MANAGER_LOADED notification. | |
435 content::NotificationService::current()->Notify( | |
436 chrome::NOTIFICATION_APP_NOTIFICATION_MANAGER_LOADED, | |
437 content::Source<AppNotificationManager>(this), | |
438 content::NotificationService::NoDetails()); | |
439 } | |
440 | |
441 void AppNotificationManager::SaveOnFileThread(const std::string& extension_id, | |
442 AppNotificationList* list) { | |
443 // Own the |list|. | |
444 scoped_ptr<AppNotificationList> scoped_list(list); | |
445 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
446 storage_->Set(extension_id, *scoped_list); | |
447 } | |
448 | |
449 void AppNotificationManager::DeleteOnFileThread( | |
450 const std::string& extension_id) { | |
451 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
452 storage_->Delete(extension_id); | |
453 } | |
454 | |
455 AppNotificationList& AppNotificationManager::GetAllInternal( | |
456 const std::string& extension_id) { | |
457 NotificationMap::iterator found = notifications_->find(extension_id); | |
458 if (found == notifications_->end()) { | |
459 (*notifications_)[extension_id] = AppNotificationList(); | |
460 found = notifications_->find(extension_id); | |
461 } | |
462 CHECK(found != notifications_->end()); | |
463 return found->second; | |
464 } | |
465 | |
466 void AppNotificationManager::Remove(const std::string& extension_id, | |
467 const std::string& guid) { | |
468 DCHECK(loaded()); | |
469 AppNotificationList& list = GetAllInternal(extension_id); | |
470 RemoveByGuid(&list, guid); | |
471 | |
472 if (storage_.get()) { | |
473 BrowserThread::PostTask( | |
474 BrowserThread::FILE, | |
475 FROM_HERE, | |
476 base::Bind(&AppNotificationManager::SaveOnFileThread, | |
477 this, extension_id, CopyAppNotificationList(list))); | |
478 } | |
479 | |
480 content::NotificationService::current()->Notify( | |
481 chrome::NOTIFICATION_APP_NOTIFICATION_STATE_CHANGED, | |
482 content::Source<Profile>(profile_), | |
483 content::Details<const std::string>(&extension_id)); | |
484 } | |
485 | |
486 const AppNotification* AppNotificationManager::GetNotification( | |
487 const std::string& extension_id, const std::string& guid) { | |
488 DCHECK(loaded()); | |
489 const AppNotificationList& list = GetAllInternal(extension_id); | |
490 return FindByGuid(list, guid); | |
491 } | |
492 | |
493 void AppNotificationManager::SyncAddChange(const AppNotification& notif) { | |
494 // Skip if either: | |
495 // - Notification is marked as local. | |
496 // - Sync is not enabled by user. | |
497 // - Change is generated from within the manager. | |
498 if (notif.is_local() || !models_associated_ || processing_syncer_changes_) | |
499 return; | |
500 | |
501 // TODO(munjal): crbug.com/10059. Work with Lingesh/Antony to resolve. | |
502 | |
503 syncer::SyncChangeList changes; | |
504 syncer::SyncData sync_data = CreateSyncDataFromNotification(notif); | |
505 changes.push_back( | |
506 syncer::SyncChange(FROM_HERE, | |
507 syncer::SyncChange::ACTION_ADD, | |
508 sync_data)); | |
509 sync_processor_->ProcessSyncChanges(FROM_HERE, changes); | |
510 } | |
511 | |
512 void AppNotificationManager::SyncRemoveChange(const AppNotification& notif) { | |
513 // Skip if either: | |
514 // - Sync is not enabled by user. | |
515 // - Change is generated from within the manager. | |
516 if (notif.is_local() || !models_associated_) { | |
517 return; | |
518 } | |
519 | |
520 syncer::SyncChangeList changes; | |
521 syncer::SyncData sync_data = CreateSyncDataFromNotification(notif); | |
522 changes.push_back( | |
523 syncer::SyncChange(FROM_HERE, | |
524 syncer::SyncChange::ACTION_DELETE, | |
525 sync_data)); | |
526 sync_processor_->ProcessSyncChanges(FROM_HERE, changes); | |
527 } | |
528 | |
529 void AppNotificationManager::SyncClearAllChange( | |
530 const AppNotificationList& list) { | |
531 // Skip if either: | |
532 // - Sync is not enabled by user. | |
533 // - Change is generated from within the manager. | |
534 if (!models_associated_ || processing_syncer_changes_) | |
535 return; | |
536 | |
537 syncer::SyncChangeList changes; | |
538 for (AppNotificationList::const_iterator iter = list.begin(); | |
539 iter != list.end(); ++iter) { | |
540 const AppNotification& notif = *iter->get(); | |
541 // Skip notifications marked as local. | |
542 if (notif.is_local()) | |
543 continue; | |
544 changes.push_back(syncer::SyncChange( | |
545 FROM_HERE, | |
546 syncer::SyncChange::ACTION_DELETE, | |
547 CreateSyncDataFromNotification(notif))); | |
548 } | |
549 sync_processor_->ProcessSyncChanges(FROM_HERE, changes); | |
550 } | |
551 | |
552 // static | |
553 syncer::SyncData AppNotificationManager::CreateSyncDataFromNotification( | |
554 const AppNotification& notification) { | |
555 DCHECK(!notification.is_local()); | |
556 sync_pb::EntitySpecifics specifics; | |
557 sync_pb::AppNotification* notif_specifics = | |
558 specifics.mutable_app_notification(); | |
559 notif_specifics->set_app_id(notification.extension_id()); | |
560 notif_specifics->set_creation_timestamp_ms( | |
561 notification.creation_time().ToInternalValue()); | |
562 notif_specifics->set_body_text(notification.body()); | |
563 notif_specifics->set_guid(notification.guid()); | |
564 notif_specifics->set_link_text(notification.link_text()); | |
565 notif_specifics->set_link_url(notification.link_url().spec()); | |
566 notif_specifics->set_title(notification.title()); | |
567 return syncer::SyncData::CreateLocalData( | |
568 notif_specifics->guid(), notif_specifics->app_id(), specifics); | |
569 } | |
570 | |
571 // static | |
572 AppNotification* AppNotificationManager::CreateNotificationFromSyncData( | |
573 const syncer::SyncData& sync_data) { | |
574 sync_pb::AppNotification specifics = | |
575 sync_data.GetSpecifics().app_notification(); | |
576 | |
577 // Check for mandatory fields. | |
578 if (!specifics.has_app_id() || !specifics.has_guid() || | |
579 !specifics.has_title() || !specifics.has_body_text() || | |
580 !specifics.has_creation_timestamp_ms()) { | |
581 return NULL; | |
582 } | |
583 | |
584 AppNotification* notification = new AppNotification( | |
585 false, base::Time::FromInternalValue(specifics.creation_timestamp_ms()), | |
586 specifics.guid(), specifics.app_id(), | |
587 specifics.title(), specifics.body_text()); | |
588 if (specifics.has_link_text()) | |
589 notification->set_link_text(specifics.link_text()); | |
590 if (specifics.has_link_url()) | |
591 notification->set_link_url(GURL(specifics.link_url())); | |
592 return notification; | |
593 } | |
594 | |
595 } // namespace extensions | |
OLD | NEW |