Home | History | Annotate | Download | only in sync_notifier
      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