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 // The ChromeNotifierService works together with sync to maintain the state of 6 // user notifications, which can then be presented in the notification center, 7 // via the Notification UI Manager. 8 9 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_service.h" 10 11 #include <string> 12 #include <vector> 13 14 #include "base/command_line.h" 15 #include "chrome/browser/notifications/desktop_notification_service.h" 16 #include "chrome/browser/notifications/desktop_notification_service_factory.h" 17 #include "chrome/browser/notifications/notification.h" 18 #include "chrome/browser/notifications/notification_ui_manager.h" 19 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_service_factory.h" 20 #include "chrome/browser/profiles/profile.h" 21 #include "content/public/browser/browser_thread.h" 22 #include "grit/generated_resources.h" 23 #include "grit/theme_resources.h" 24 #include "sync/api/sync_change.h" 25 #include "sync/api/sync_change_processor.h" 26 #include "sync/api/sync_error_factory.h" 27 #include "sync/protocol/sync.pb.h" 28 #include "sync/protocol/synced_notification_specifics.pb.h" 29 #include "third_party/WebKit/public/web/WebTextDirection.h" 30 #include "ui/base/l10n/l10n_util.h" 31 #include "ui/base/resource/resource_bundle.h" 32 #include "ui/message_center/notifier_settings.h" 33 #include "url/gurl.h" 34 35 namespace notifier { 36 namespace { 37 38 const char kFirstSyncedNotificationServiceId[] = "Google+"; 39 40 } 41 42 bool ChromeNotifierService::avoid_bitmap_fetching_for_test_ = false; 43 44 ChromeNotifierService::ChromeNotifierService(Profile* profile, 45 NotificationUIManager* manager) 46 : profile_(profile), notification_manager_(manager) { 47 } 48 ChromeNotifierService::~ChromeNotifierService() {} 49 50 // Methods from BrowserContextKeyedService. 51 void ChromeNotifierService::Shutdown() {} 52 53 // syncer::SyncableService implementation. 54 55 // This is called at startup to sync with the server. 56 // This code is not thread safe. 57 syncer::SyncMergeResult ChromeNotifierService::MergeDataAndStartSyncing( 58 syncer::ModelType type, 59 const syncer::SyncDataList& initial_sync_data, 60 scoped_ptr<syncer::SyncChangeProcessor> sync_processor, 61 scoped_ptr<syncer::SyncErrorFactory> error_handler) { 62 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 63 DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, type); 64 syncer::SyncMergeResult merge_result(syncer::SYNCED_NOTIFICATIONS); 65 // A list of local changes to send up to the sync server. 66 syncer::SyncChangeList new_changes; 67 sync_processor_ = sync_processor.Pass(); 68 69 for (syncer::SyncDataList::const_iterator it = initial_sync_data.begin(); 70 it != initial_sync_data.end(); ++it) { 71 const syncer::SyncData& sync_data = *it; 72 DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, sync_data.GetDataType()); 73 74 // Build a local notification object from the sync data. 75 scoped_ptr<SyncedNotification> incoming(CreateNotificationFromSyncData( 76 sync_data)); 77 if (!incoming) { 78 // TODO(petewil): Turn this into a NOTREACHED() call once we fix the 79 // underlying problem causing bad data. 80 LOG(WARNING) << "Badly formed sync data in incoming notification"; 81 continue; 82 } 83 84 // Process each incoming remote notification. 85 const std::string& key = incoming->GetKey(); 86 DCHECK_GT(key.length(), 0U); 87 SyncedNotification* found = FindNotificationById(key); 88 89 if (NULL == found) { 90 // If there are no conflicts, copy in the data from remote. 91 Add(incoming.Pass()); 92 } else { 93 // If the incoming (remote) and stored (local) notifications match 94 // in all fields, we don't need to do anything here. 95 if (incoming->EqualsIgnoringReadState(*found)) { 96 97 if (incoming->GetReadState() == found->GetReadState()) { 98 // Notification matches on the client and the server, nothing to do. 99 continue; 100 } else { 101 // If the read state is different, read wins for both places. 102 if (incoming->GetReadState() == SyncedNotification::kDismissed) { 103 // If it is marked as read on the server, but not the client. 104 found->NotificationHasBeenDismissed(); 105 // Tell the Notification UI Manager to remove it. 106 notification_manager_->CancelById(found->GetKey()); 107 } else if (incoming->GetReadState() == SyncedNotification::kRead) { 108 // If it is marked as read on the server, but not the client. 109 found->NotificationHasBeenRead(); 110 // Tell the Notification UI Manager to remove it. 111 notification_manager_->CancelById(found->GetKey()); 112 } else { 113 // If it is marked as read on the client, but not the server. 114 syncer::SyncData sync_data = CreateSyncDataFromNotification(*found); 115 new_changes.push_back( 116 syncer::SyncChange(FROM_HERE, 117 syncer::SyncChange::ACTION_UPDATE, 118 sync_data)); 119 } 120 // If local state changed, notify Notification UI Manager. 121 } 122 // For any other conflict besides read state, treat it as an update. 123 } else { 124 // If different, just replace the local with the remote. 125 // TODO(petewil): Someday we may allow changes from the client to 126 // flow upwards, when we do, we will need better merge resolution. 127 found->Update(sync_data); 128 129 // Tell the notification manager to update the notification. 130 UpdateInMessageCenter(found); 131 } 132 } 133 } 134 135 // Send up the changes that were made locally. 136 if (new_changes.size() > 0) { 137 merge_result.set_error(sync_processor_->ProcessSyncChanges( 138 FROM_HERE, new_changes)); 139 } 140 141 return merge_result; 142 } 143 144 void ChromeNotifierService::StopSyncing(syncer::ModelType type) { 145 DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, type); 146 // TODO(petewil): implement 147 } 148 149 syncer::SyncDataList ChromeNotifierService::GetAllSyncData( 150 syncer::ModelType type) const { 151 DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, type); 152 syncer::SyncDataList sync_data; 153 154 // Copy our native format data into a SyncDataList format. 155 ScopedVector<SyncedNotification>::const_iterator it = 156 notification_data_.begin(); 157 for (; it != notification_data_.end(); ++it) { 158 sync_data.push_back(CreateSyncDataFromNotification(**it)); 159 } 160 161 return sync_data; 162 } 163 164 // This method is called when there is an incoming sync change from the server. 165 syncer::SyncError ChromeNotifierService::ProcessSyncChanges( 166 const tracked_objects::Location& from_here, 167 const syncer::SyncChangeList& change_list) { 168 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 169 syncer::SyncError error; 170 171 for (syncer::SyncChangeList::const_iterator it = change_list.begin(); 172 it != change_list.end(); ++it) { 173 syncer::SyncData sync_data = it->sync_data(); 174 DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, sync_data.GetDataType()); 175 syncer::SyncChange::SyncChangeType change_type = it->change_type(); 176 177 scoped_ptr<SyncedNotification> new_notification( 178 CreateNotificationFromSyncData(sync_data)); 179 if (!new_notification.get()) { 180 NOTREACHED() << "Failed to read notification."; 181 continue; 182 } 183 184 const std::string& key = new_notification->GetKey(); 185 DCHECK_GT(key.length(), 0U); 186 SyncedNotification* found = FindNotificationById(key); 187 188 switch (change_type) { 189 case syncer::SyncChange::ACTION_ADD: 190 // Intentional fall through, cases are identical. 191 case syncer::SyncChange::ACTION_UPDATE: 192 if (found == NULL) { 193 Add(new_notification.Pass()); 194 break; 195 } 196 // Update it in our store. 197 found->Update(sync_data); 198 // Tell the notification manager to update the notification. 199 UpdateInMessageCenter(found); 200 break; 201 202 case syncer::SyncChange::ACTION_DELETE: 203 if (found == NULL) { 204 break; 205 } 206 // Remove it from our store. 207 FreeNotificationById(key); 208 // Remove it from the message center. 209 UpdateInMessageCenter(new_notification.get()); 210 // TODO(petewil): Do I need to remember that it was deleted in case the 211 // add arrives after the delete? If so, how long do I need to remember? 212 break; 213 214 default: 215 NOTREACHED(); 216 break; 217 } 218 } 219 220 return error; 221 } 222 223 // Support functions for data type conversion. 224 225 // Static method. Get to the sync data in our internal format. 226 syncer::SyncData ChromeNotifierService::CreateSyncDataFromNotification( 227 const SyncedNotification& notification) { 228 // Construct the sync_data using the specifics from the notification. 229 return syncer::SyncData::CreateLocalData( 230 notification.GetKey(), notification.GetKey(), 231 notification.GetEntitySpecifics()); 232 } 233 234 // Static Method. Convert from SyncData to our internal format. 235 scoped_ptr<SyncedNotification> 236 ChromeNotifierService::CreateNotificationFromSyncData( 237 const syncer::SyncData& sync_data) { 238 // Get a pointer to our data within the sync_data object. 239 sync_pb::SyncedNotificationSpecifics specifics = 240 sync_data.GetSpecifics().synced_notification(); 241 242 // Check for mandatory fields in the sync_data object. 243 if (!specifics.has_coalesced_notification() || 244 !specifics.coalesced_notification().has_key() || 245 !specifics.coalesced_notification().has_read_state()) { 246 DVLOG(1) << "Synced Notification missing mandatory fields " 247 << "has coalesced notification? " 248 << specifics.has_coalesced_notification() 249 << " has key? " << specifics.coalesced_notification().has_key() 250 << " has read state? " 251 << specifics.coalesced_notification().has_read_state(); 252 return scoped_ptr<SyncedNotification>(); 253 } 254 255 bool is_well_formed_unread_notification = 256 (static_cast<SyncedNotification::ReadState>( 257 specifics.coalesced_notification().read_state()) == 258 SyncedNotification::kUnread && 259 specifics.coalesced_notification().has_render_info()); 260 bool is_well_formed_read_notification = 261 (static_cast<SyncedNotification::ReadState>( 262 specifics.coalesced_notification().read_state()) == 263 SyncedNotification::kRead && 264 specifics.coalesced_notification().has_render_info()); 265 bool is_well_formed_dismissed_notification = 266 (static_cast<SyncedNotification::ReadState>( 267 specifics.coalesced_notification().read_state()) == 268 SyncedNotification::kDismissed); 269 270 // If the notification is poorly formed, return a null pointer. 271 if (!is_well_formed_unread_notification && 272 !is_well_formed_read_notification && 273 !is_well_formed_dismissed_notification) { 274 DVLOG(1) << "Synced Notification is not well formed." 275 << " unread well formed? " 276 << is_well_formed_unread_notification 277 << " dismissed well formed? " 278 << is_well_formed_dismissed_notification 279 << " read well formed? " 280 << is_well_formed_read_notification; 281 return scoped_ptr<SyncedNotification>(); 282 } 283 284 // Create a new notification object based on the supplied sync_data. 285 scoped_ptr<SyncedNotification> notification( 286 new SyncedNotification(sync_data)); 287 288 return notification.Pass(); 289 } 290 291 // This returns a pointer into a vector that we own. Caller must not free it. 292 // Returns NULL if no match is found. 293 SyncedNotification* ChromeNotifierService::FindNotificationById( 294 const std::string& notification_id) { 295 // TODO(petewil): We can make a performance trade off here. 296 // While the vector has good locality of reference, a map has faster lookup. 297 // Based on how big we expect this to get, maybe change this to a map. 298 ScopedVector<SyncedNotification>::const_iterator it = 299 notification_data_.begin(); 300 for (; it != notification_data_.end(); ++it) { 301 SyncedNotification* notification = *it; 302 if (notification_id == notification->GetKey()) 303 return *it; 304 } 305 306 return NULL; 307 } 308 309 void ChromeNotifierService::FreeNotificationById( 310 const std::string& notification_id) { 311 ScopedVector<SyncedNotification>::iterator it = notification_data_.begin(); 312 for (; it != notification_data_.end(); ++it) { 313 SyncedNotification* notification = *it; 314 if (notification_id == notification->GetKey()) { 315 notification_data_.erase(it); 316 return; 317 } 318 } 319 } 320 321 void ChromeNotifierService::GetSyncedNotificationServices( 322 std::vector<message_center::Notifier*>* notifiers) { 323 // TODO(mukai|petewil): Check the profile's eligibility before adding the 324 // sample app. 325 326 // TODO(petewil): Really obtain the list of synced notification sending 327 // services from the server and create the list of ids here. Until then, we 328 // are hardcoding the service names. Once that is done, remove this 329 // hardcoding. 330 // crbug.com/248337 331 DesktopNotificationService* desktop_notification_service = 332 DesktopNotificationServiceFactory::GetForProfile(profile_); 333 message_center::NotifierId notifier_id( 334 message_center::NotifierId::SYNCED_NOTIFICATION_SERVICE, 335 kFirstSyncedNotificationServiceId); 336 message_center::Notifier* notifier_service = new message_center::Notifier( 337 notifier_id, 338 l10n_util::GetStringUTF16( 339 IDS_FIRST_SYNCED_NOTIFICATION_SERVICE_NAME), 340 desktop_notification_service->IsNotifierEnabled(notifier_id)); 341 342 // Add icons for our sending services. 343 // TODO(petewil): Replace this temporary hardcoding with a new sync datatype 344 // to dynamically get the name and icon for each synced notification sending 345 // service. Until then, we use hardcoded service icons for all services. 346 // crbug.com/248337 347 notifier_service->icon = ui::ResourceBundle::GetSharedInstance(). 348 GetImageNamed(IDR_TEMPORARY_GOOGLE_PLUS_ICON); 349 350 notifiers->push_back(notifier_service); 351 } 352 353 void ChromeNotifierService::MarkNotificationAsRead( 354 const std::string& key) { 355 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 356 SyncedNotification* notification = FindNotificationById(key); 357 CHECK(notification != NULL); 358 359 notification->NotificationHasBeenRead(); 360 syncer::SyncChangeList new_changes; 361 362 syncer::SyncData sync_data = CreateSyncDataFromNotification(*notification); 363 new_changes.push_back( 364 syncer::SyncChange(FROM_HERE, 365 syncer::SyncChange::ACTION_UPDATE, 366 sync_data)); 367 368 // Send up the changes that were made locally. 369 sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes); 370 } 371 372 // Add a new notification to our data structure. This takes ownership 373 // of the passed in pointer. 374 void ChromeNotifierService::Add(scoped_ptr<SyncedNotification> notification) { 375 SyncedNotification* notification_copy = notification.get(); 376 // Take ownership of the object and put it into our local storage. 377 notification_data_.push_back(notification.release()); 378 379 // If the user is not interested in this type of notification, ignore it. 380 std::vector<std::string>::iterator iter = 381 find(enabled_sending_services_.begin(), 382 enabled_sending_services_.end(), 383 notification_copy->GetSendingServiceId()); 384 if (iter == enabled_sending_services_.end()) { 385 return; 386 } 387 388 UpdateInMessageCenter(notification_copy); 389 } 390 391 void ChromeNotifierService::AddForTest( 392 scoped_ptr<notifier::SyncedNotification> notification) { 393 notification_data_.push_back(notification.release()); 394 } 395 396 void ChromeNotifierService::UpdateInMessageCenter( 397 SyncedNotification* notification) { 398 // If the feature is disabled, exit now. 399 if (!notifier::ChromeNotifierServiceFactory::UseSyncedNotifications( 400 CommandLine::ForCurrentProcess())) 401 return; 402 403 notification->LogNotification(); 404 405 if (notification->GetReadState() == SyncedNotification::kUnread) { 406 // If the message is unread, update it. 407 Display(notification); 408 } else { 409 // If the message is read or deleted, dismiss it from the center. 410 // We intentionally ignore errors if it is not in the center. 411 notification_manager_->CancelById(notification->GetKey()); 412 } 413 } 414 415 void ChromeNotifierService::Display(SyncedNotification* notification) { 416 // Set up to fetch the bitmaps. 417 notification->QueueBitmapFetchJobs(notification_manager_, 418 this, 419 profile_); 420 421 // Our tests cannot use the network for reliability reasons. 422 if (avoid_bitmap_fetching_for_test_) { 423 return; 424 } 425 426 // Start the bitmap fetching, Show() will be called when the last bitmap 427 // either arrives or times out. 428 notification->StartBitmapFetch(); 429 } 430 431 void ChromeNotifierService::OnSyncedNotificationServiceEnabled( 432 const std::string& notifier_id, bool enabled) { 433 std::vector<std::string>::iterator iter; 434 435 iter = find(enabled_sending_services_.begin(), 436 enabled_sending_services_.end(), 437 notifier_id); 438 439 // Add the notifier_id if it is enabled and not already there. 440 if (iter == enabled_sending_services_.end() && enabled) { 441 enabled_sending_services_.push_back(notifier_id); 442 // TODO(petewil) Check now for any outstanding notifications. 443 // Remove the notifier_id if it is disabled and present. 444 } else if (iter != enabled_sending_services_.end() && !enabled) { 445 enabled_sending_services_.erase(iter); 446 } 447 448 // Otherwise, nothing to do, we can exit. 449 return; 450 } 451 452 } // namespace notifier 453