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