Home | History | Annotate | Download | only in app_list
      1 // Copyright 2013 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/ui/app_list/app_list_syncable_service.h"
      6 
      7 #include "base/command_line.h"
      8 #include "chrome/browser/apps/drive/drive_app_provider.h"
      9 #include "chrome/browser/chrome_notification_types.h"
     10 #include "chrome/browser/extensions/extension_service.h"
     11 #include "chrome/browser/profiles/profile.h"
     12 #include "chrome/browser/ui/app_list/app_list_service.h"
     13 #include "chrome/browser/ui/app_list/extension_app_item.h"
     14 #include "chrome/browser/ui/app_list/extension_app_model_builder.h"
     15 #include "chrome/browser/ui/host_desktop.h"
     16 #include "chrome/common/chrome_switches.h"
     17 #include "chrome/common/extensions/extension_constants.h"
     18 #include "chrome/grit/generated_resources.h"
     19 #include "content/public/browser/notification_source.h"
     20 #include "extensions/browser/extension_prefs.h"
     21 #include "extensions/browser/extension_system.h"
     22 #include "extensions/browser/uninstall_reason.h"
     23 #include "extensions/common/constants.h"
     24 #include "sync/api/sync_change_processor.h"
     25 #include "sync/api/sync_data.h"
     26 #include "sync/api/sync_merge_result.h"
     27 #include "sync/protocol/sync.pb.h"
     28 #include "ui/app_list/app_list_folder_item.h"
     29 #include "ui/app_list/app_list_item.h"
     30 #include "ui/app_list/app_list_model.h"
     31 #include "ui/app_list/app_list_model_observer.h"
     32 #include "ui/app_list/app_list_switches.h"
     33 #include "ui/base/l10n/l10n_util.h"
     34 
     35 #if defined(OS_CHROMEOS)
     36 #include "chrome/browser/chromeos/file_manager/app_id.h"
     37 #include "chrome/browser/chromeos/genius_app/app_id.h"
     38 #endif
     39 
     40 using syncer::SyncChange;
     41 
     42 namespace app_list {
     43 
     44 namespace {
     45 
     46 const char kOemFolderId[] = "ddb1da55-d478-4243-8642-56d3041f0263";
     47 
     48 void UpdateSyncItemFromSync(const sync_pb::AppListSpecifics& specifics,
     49                             AppListSyncableService::SyncItem* item) {
     50   DCHECK_EQ(item->item_id, specifics.item_id());
     51   item->item_type = specifics.item_type();
     52   item->item_name = specifics.item_name();
     53   item->parent_id = specifics.parent_id();
     54   if (!specifics.page_ordinal().empty())
     55     item->page_ordinal = syncer::StringOrdinal(specifics.page_ordinal());
     56   if (!specifics.item_ordinal().empty())
     57     item->item_ordinal = syncer::StringOrdinal(specifics.item_ordinal());
     58 }
     59 
     60 bool UpdateSyncItemFromAppItem(const AppListItem* app_item,
     61                                AppListSyncableService::SyncItem* sync_item) {
     62   DCHECK_EQ(sync_item->item_id, app_item->id());
     63   bool changed = false;
     64   if (app_list::switches::IsFolderUIEnabled() &&
     65       sync_item->parent_id != app_item->folder_id()) {
     66     sync_item->parent_id = app_item->folder_id();
     67     changed = true;
     68   }
     69   if (sync_item->item_name != app_item->name()) {
     70     sync_item->item_name = app_item->name();
     71     changed = true;
     72   }
     73   if (!sync_item->item_ordinal.IsValid() ||
     74       !app_item->position().Equals(sync_item->item_ordinal)) {
     75     sync_item->item_ordinal = app_item->position();
     76     changed = true;
     77   }
     78   // TODO(stevenjb): Set page_ordinal.
     79   return changed;
     80 }
     81 
     82 void GetSyncSpecificsFromSyncItem(const AppListSyncableService::SyncItem* item,
     83                                   sync_pb::AppListSpecifics* specifics) {
     84   DCHECK(specifics);
     85   specifics->set_item_id(item->item_id);
     86   specifics->set_item_type(item->item_type);
     87   specifics->set_item_name(item->item_name);
     88   specifics->set_parent_id(item->parent_id);
     89   if (item->page_ordinal.IsValid())
     90     specifics->set_page_ordinal(item->page_ordinal.ToInternalValue());
     91   if (item->item_ordinal.IsValid())
     92     specifics->set_item_ordinal(item->item_ordinal.ToInternalValue());
     93 }
     94 
     95 syncer::SyncData GetSyncDataFromSyncItem(
     96     const AppListSyncableService::SyncItem* item) {
     97   sync_pb::EntitySpecifics specifics;
     98   GetSyncSpecificsFromSyncItem(item, specifics.mutable_app_list());
     99   return syncer::SyncData::CreateLocalData(item->item_id,
    100                                            item->item_id,
    101                                            specifics);
    102 }
    103 
    104 bool AppIsDefault(ExtensionService* service, const std::string& id) {
    105   return service && extensions::ExtensionPrefs::Get(service->profile())
    106                         ->WasInstalledByDefault(id);
    107 }
    108 
    109 bool IsUnRemovableDefaultApp(const std::string& id) {
    110   if (id == extension_misc::kChromeAppId ||
    111       id == extensions::kWebStoreAppId)
    112     return true;
    113 #if defined(OS_CHROMEOS)
    114   if (id == file_manager::kFileManagerAppId || id == genius_app::kGeniusAppId)
    115     return true;
    116 #endif
    117   return false;
    118 }
    119 
    120 void UninstallExtension(ExtensionService* service, const std::string& id) {
    121   if (service && service->GetInstalledExtension(id)) {
    122     service->UninstallExtension(id,
    123                                 extensions::UNINSTALL_REASON_SYNC,
    124                                 base::Bind(&base::DoNothing),
    125                                 NULL);
    126   }
    127 }
    128 
    129 bool GetAppListItemType(AppListItem* item,
    130                         sync_pb::AppListSpecifics::AppListItemType* type) {
    131   const char* item_type = item->GetItemType();
    132   if (item_type == ExtensionAppItem::kItemType) {
    133     *type = sync_pb::AppListSpecifics::TYPE_APP;
    134   } else if (item_type == AppListFolderItem::kItemType) {
    135     *type = sync_pb::AppListSpecifics::TYPE_FOLDER;
    136   } else {
    137     LOG(ERROR) << "Unrecognized model type: " << item_type;
    138     return false;
    139   }
    140   return true;
    141 }
    142 
    143 }  // namespace
    144 
    145 // AppListSyncableService::SyncItem
    146 
    147 AppListSyncableService::SyncItem::SyncItem(
    148     const std::string& id,
    149     sync_pb::AppListSpecifics::AppListItemType type)
    150     : item_id(id),
    151       item_type(type) {
    152 }
    153 
    154 AppListSyncableService::SyncItem::~SyncItem() {
    155 }
    156 
    157 // AppListSyncableService::ModelObserver
    158 
    159 class AppListSyncableService::ModelObserver : public AppListModelObserver {
    160  public:
    161   explicit ModelObserver(AppListSyncableService* owner)
    162       : owner_(owner),
    163         adding_item_(NULL) {
    164     DVLOG(2) << owner_ << ": ModelObserver Added";
    165     owner_->model()->AddObserver(this);
    166   }
    167 
    168   virtual ~ModelObserver() {
    169     owner_->model()->RemoveObserver(this);
    170     DVLOG(2) << owner_ << ": ModelObserver Removed";
    171   }
    172 
    173  private:
    174   // AppListModelObserver
    175   virtual void OnAppListItemAdded(AppListItem* item) OVERRIDE {
    176     DCHECK(!adding_item_);
    177     adding_item_ = item;  // Ignore updates while adding an item.
    178     VLOG(2) << owner_ << " OnAppListItemAdded: " << item->ToDebugString();
    179     owner_->AddOrUpdateFromSyncItem(item);
    180     adding_item_ = NULL;
    181   }
    182 
    183   virtual void OnAppListItemWillBeDeleted(AppListItem* item) OVERRIDE {
    184     DCHECK(!adding_item_);
    185     VLOG(2) << owner_ << " OnAppListItemDeleted: " << item->ToDebugString();
    186     // Don't sync folder removal in case the folder still exists on another
    187     // device (e.g. with device specific items in it). Empty folders will be
    188     // deleted when the last item is removed (in PruneEmptySyncFolders()).
    189     if (item->GetItemType() == AppListFolderItem::kItemType)
    190       return;
    191     owner_->RemoveSyncItem(item->id());
    192   }
    193 
    194   virtual void OnAppListItemUpdated(AppListItem* item) OVERRIDE {
    195     if (adding_item_) {
    196       // Adding an item may trigger update notifications which should be
    197       // ignored.
    198       DCHECK_EQ(adding_item_, item);
    199       return;
    200     }
    201     VLOG(2) << owner_ << " OnAppListItemUpdated: " << item->ToDebugString();
    202     owner_->UpdateSyncItem(item);
    203   }
    204 
    205   AppListSyncableService* owner_;
    206   AppListItem* adding_item_;  // Unowned pointer to item being added.
    207 
    208   DISALLOW_COPY_AND_ASSIGN(ModelObserver);
    209 };
    210 
    211 // AppListSyncableService
    212 
    213 AppListSyncableService::AppListSyncableService(
    214     Profile* profile,
    215     extensions::ExtensionSystem* extension_system)
    216     : profile_(profile),
    217       extension_system_(extension_system),
    218       model_(new AppListModel),
    219       initial_sync_data_processed_(false),
    220       first_app_list_sync_(true) {
    221   if (!extension_system) {
    222     LOG(ERROR) << "AppListSyncableService created with no ExtensionSystem";
    223     return;
    224   }
    225 
    226   oem_folder_name_ =
    227       l10n_util::GetStringUTF8(IDS_APP_LIST_OEM_DEFAULT_FOLDER_NAME);
    228 
    229   // Note: model_observer_ is constructed after the initial sync changes are
    230   // received in MergeDataAndStartSyncing(). Changes to the model before that
    231   // will be synced after the initial sync occurs.
    232   if (extension_system->extension_service() &&
    233       extension_system->extension_service()->is_ready()) {
    234     BuildModel();
    235     return;
    236   }
    237 
    238   // The extensions for this profile have not yet all been loaded.
    239   registrar_.Add(this,
    240                  extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
    241                  content::Source<Profile>(profile));
    242 }
    243 
    244 AppListSyncableService::~AppListSyncableService() {
    245   // Remove observers.
    246   model_observer_.reset();
    247 
    248   STLDeleteContainerPairSecondPointers(sync_items_.begin(), sync_items_.end());
    249 }
    250 
    251 void AppListSyncableService::BuildModel() {
    252   // For now, use the AppListControllerDelegate associated with the native
    253   // desktop. TODO(stevenjb): Remove ExtensionAppModelBuilder controller
    254   // dependency and move the dependent methods from AppListControllerDelegate
    255   // to an extension service delegate associated with this class.
    256   AppListControllerDelegate* controller = NULL;
    257   AppListService* service =
    258       AppListService::Get(chrome::HOST_DESKTOP_TYPE_NATIVE);
    259   if (service)
    260     controller = service->GetControllerDelegate();
    261   apps_builder_.reset(new ExtensionAppModelBuilder(controller));
    262   DCHECK(profile_);
    263   if (app_list::switches::IsAppListSyncEnabled()) {
    264     VLOG(1) << this << ": AppListSyncableService: InitializeWithService.";
    265     SyncStarted();
    266     apps_builder_->InitializeWithService(this);
    267   } else {
    268     VLOG(1) << this << ": AppListSyncableService: InitializeWithProfile.";
    269     apps_builder_->InitializeWithProfile(profile_, model_.get());
    270   }
    271 
    272   if (app_list::switches::IsDriveAppsInAppListEnabled())
    273     drive_app_provider_.reset(new DriveAppProvider(profile_));
    274 }
    275 
    276 void AppListSyncableService::ResetDriveAppProviderForTest() {
    277   drive_app_provider_.reset();
    278 }
    279 
    280 void AppListSyncableService::Shutdown() {
    281   // DriveAppProvider touches other KeyedServices in its dtor and needs be
    282   // released in shutdown stage.
    283   drive_app_provider_.reset();
    284 }
    285 
    286 void AppListSyncableService::Observe(
    287     int type,
    288     const content::NotificationSource& source,
    289     const content::NotificationDetails& details) {
    290   DCHECK_EQ(extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED, type);
    291   DCHECK_EQ(profile_, content::Source<Profile>(source).ptr());
    292   registrar_.RemoveAll();
    293   BuildModel();
    294 }
    295 
    296 const AppListSyncableService::SyncItem*
    297 AppListSyncableService::GetSyncItem(const std::string& id) const {
    298   SyncItemMap::const_iterator iter = sync_items_.find(id);
    299   if (iter != sync_items_.end())
    300     return iter->second;
    301   return NULL;
    302 }
    303 
    304 void AppListSyncableService::SetOemFolderName(const std::string& name) {
    305   oem_folder_name_ = name;
    306   AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
    307   if (oem_folder)
    308     model_->SetItemName(oem_folder, oem_folder_name_);
    309 }
    310 
    311 void AppListSyncableService::AddItem(scoped_ptr<AppListItem> app_item) {
    312   SyncItem* sync_item = FindOrAddSyncItem(app_item.get());
    313   if (!sync_item)
    314     return;  // Item is not valid.
    315 
    316   std::string folder_id;
    317   if (app_list::switches::IsFolderUIEnabled()) {
    318     if (AppIsOem(app_item->id())) {
    319       folder_id = FindOrCreateOemFolder();
    320       VLOG_IF(2, !folder_id.empty())
    321           << this << ": AddItem to OEM folder: " << sync_item->ToString();
    322     } else {
    323       folder_id = sync_item->parent_id;
    324     }
    325   }
    326   VLOG(2) << this << ": AddItem: " << sync_item->ToString()
    327           << " Folder: '" << folder_id << "'";
    328   model_->AddItemToFolder(app_item.Pass(), folder_id);
    329 }
    330 
    331 AppListSyncableService::SyncItem* AppListSyncableService::FindOrAddSyncItem(
    332     AppListItem* app_item) {
    333   const std::string& item_id = app_item->id();
    334   if (item_id.empty()) {
    335     LOG(ERROR) << "AppListItem item with empty ID";
    336     return NULL;
    337   }
    338   SyncItem* sync_item = FindSyncItem(item_id);
    339   if (sync_item) {
    340     // If there is an existing, non-REMOVE_DEFAULT entry, return it.
    341     if (sync_item->item_type !=
    342         sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
    343       DVLOG(2) << this << ": AddItem already exists: " << sync_item->ToString();
    344       return sync_item;
    345     }
    346 
    347     if (RemoveDefaultApp(app_item, sync_item))
    348       return NULL;
    349 
    350     // Fall through. The REMOVE_DEFAULT_APP entry has been deleted, now a new
    351     // App entry can be added.
    352   }
    353 
    354   return CreateSyncItemFromAppItem(app_item);
    355 }
    356 
    357 AppListSyncableService::SyncItem*
    358 AppListSyncableService::CreateSyncItemFromAppItem(AppListItem* app_item) {
    359   sync_pb::AppListSpecifics::AppListItemType type;
    360   if (!GetAppListItemType(app_item, &type))
    361     return NULL;
    362   VLOG(2) << this << " CreateSyncItemFromAppItem:" << app_item->ToDebugString();
    363   SyncItem* sync_item = CreateSyncItem(app_item->id(), type);
    364   UpdateSyncItemFromAppItem(app_item, sync_item);
    365   SendSyncChange(sync_item, SyncChange::ACTION_ADD);
    366   return sync_item;
    367 }
    368 
    369 void AppListSyncableService::AddOrUpdateFromSyncItem(AppListItem* app_item) {
    370   // Do not create a sync item for the OEM folder here, do that in
    371   // ResolveFolderPositions once the position has been resolved.
    372   if (app_item->id() == kOemFolderId)
    373     return;
    374 
    375   SyncItem* sync_item = FindSyncItem(app_item->id());
    376   if (sync_item) {
    377     UpdateAppItemFromSyncItem(sync_item, app_item);
    378     return;
    379   }
    380   CreateSyncItemFromAppItem(app_item);
    381 }
    382 
    383 bool AppListSyncableService::RemoveDefaultApp(AppListItem* item,
    384                                               SyncItem* sync_item) {
    385   CHECK_EQ(sync_item->item_type,
    386            sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP);
    387 
    388   // If there is an existing REMOVE_DEFAULT_APP entry, and the app is
    389   // installed as a Default app, uninstall the app instead of adding it.
    390   if (sync_item->item_type == sync_pb::AppListSpecifics::TYPE_APP &&
    391       AppIsDefault(extension_system_->extension_service(), item->id())) {
    392     VLOG(2) << this << ": HandleDefaultApp: Uninstall: "
    393             << sync_item->ToString();
    394     UninstallExtension(extension_system_->extension_service(), item->id());
    395     return true;
    396   }
    397 
    398   // Otherwise, we are adding the app as a non-default app (i.e. an app that
    399   // was installed by Default and removed is getting installed explicitly by
    400   // the user), so delete the REMOVE_DEFAULT_APP.
    401   DeleteSyncItem(sync_item);
    402   return false;
    403 }
    404 
    405 void AppListSyncableService::DeleteSyncItem(SyncItem* sync_item) {
    406   if (SyncStarted()) {
    407     VLOG(2) << this << " -> SYNC DELETE: " << sync_item->ToString();
    408     SyncChange sync_change(FROM_HERE, SyncChange::ACTION_DELETE,
    409                            GetSyncDataFromSyncItem(sync_item));
    410     sync_processor_->ProcessSyncChanges(
    411         FROM_HERE, syncer::SyncChangeList(1, sync_change));
    412   }
    413   std::string item_id = sync_item->item_id;
    414   delete sync_item;
    415   sync_items_.erase(item_id);
    416 }
    417 
    418 void AppListSyncableService::UpdateSyncItem(AppListItem* app_item) {
    419   SyncItem* sync_item = FindSyncItem(app_item->id());
    420   if (!sync_item) {
    421     LOG(ERROR) << "UpdateItem: no sync item: " << app_item->id();
    422     return;
    423   }
    424   bool changed = UpdateSyncItemFromAppItem(app_item, sync_item);
    425   if (!changed) {
    426     DVLOG(2) << this << " - Update: SYNC NO CHANGE: " << sync_item->ToString();
    427     return;
    428   }
    429   SendSyncChange(sync_item, SyncChange::ACTION_UPDATE);
    430 }
    431 
    432 void AppListSyncableService::RemoveItem(const std::string& id) {
    433   RemoveSyncItem(id);
    434   model_->DeleteItem(id);
    435   PruneEmptySyncFolders();
    436 }
    437 
    438 void AppListSyncableService::UpdateItem(AppListItem* app_item) {
    439   // Check to see if the item needs to be moved to/from the OEM folder.
    440   if (!app_list::switches::IsFolderUIEnabled())
    441     return;
    442   bool is_oem = AppIsOem(app_item->id());
    443   if (!is_oem && app_item->folder_id() == kOemFolderId)
    444     model_->MoveItemToFolder(app_item, "");
    445   else if (is_oem && app_item->folder_id() != kOemFolderId)
    446     model_->MoveItemToFolder(app_item, kOemFolderId);
    447 }
    448 
    449 void AppListSyncableService::RemoveSyncItem(const std::string& id) {
    450   VLOG(2) << this << ": RemoveSyncItem: " << id.substr(0, 8);
    451   SyncItemMap::iterator iter = sync_items_.find(id);
    452   if (iter == sync_items_.end()) {
    453     DVLOG(2) << this << " : RemoveSyncItem: No Item.";
    454     return;
    455   }
    456 
    457   // Check for existing RemoveDefault sync item.
    458   SyncItem* sync_item = iter->second;
    459   sync_pb::AppListSpecifics::AppListItemType type = sync_item->item_type;
    460   if (type == sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
    461     // RemoveDefault item exists, just return.
    462     DVLOG(2) << this << " : RemoveDefault Item exists.";
    463     return;
    464   }
    465 
    466   if (type == sync_pb::AppListSpecifics::TYPE_APP &&
    467       AppIsDefault(extension_system_->extension_service(), id)) {
    468     // This is a Default app; update the entry to a REMOVE_DEFAULT entry. This
    469     // will overwrite any existing entry for the item.
    470     VLOG(2) << this << " -> SYNC UPDATE: REMOVE_DEFAULT: "
    471             << sync_item->item_id;
    472     sync_item->item_type = sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP;
    473     SendSyncChange(sync_item, SyncChange::ACTION_UPDATE);
    474     return;
    475   }
    476 
    477   DeleteSyncItem(sync_item);
    478 }
    479 
    480 void AppListSyncableService::ResolveFolderPositions() {
    481   if (!app_list::switches::IsFolderUIEnabled())
    482     return;
    483 
    484   VLOG(1) << "ResolveFolderPositions.";
    485   for (SyncItemMap::iterator iter = sync_items_.begin();
    486        iter != sync_items_.end(); ++iter) {
    487     SyncItem* sync_item = iter->second;
    488     if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
    489       continue;
    490     AppListItem* app_item = model_->FindItem(sync_item->item_id);
    491     if (!app_item)
    492       continue;
    493     UpdateAppItemFromSyncItem(sync_item, app_item);
    494   }
    495 
    496   // Move the OEM folder if one exists and we have not synced its position.
    497   AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
    498   if (oem_folder && !FindSyncItem(kOemFolderId)) {
    499     model_->SetItemPosition(oem_folder, GetOemFolderPos());
    500     VLOG(1) << "Creating new OEM folder sync item: "
    501             << oem_folder->position().ToDebugString();
    502     CreateSyncItemFromAppItem(oem_folder);
    503   }
    504 }
    505 
    506 void AppListSyncableService::PruneEmptySyncFolders() {
    507   if (!app_list::switches::IsFolderUIEnabled())
    508     return;
    509 
    510   std::set<std::string> parent_ids;
    511   for (SyncItemMap::iterator iter = sync_items_.begin();
    512        iter != sync_items_.end(); ++iter) {
    513     parent_ids.insert(iter->second->parent_id);
    514   }
    515   for (SyncItemMap::iterator iter = sync_items_.begin();
    516        iter != sync_items_.end(); ) {
    517     SyncItem* sync_item = (iter++)->second;
    518     if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
    519       continue;
    520     if (!ContainsKey(parent_ids, sync_item->item_id))
    521       DeleteSyncItem(sync_item);
    522   }
    523 }
    524 
    525 // AppListSyncableService syncer::SyncableService
    526 
    527 syncer::SyncMergeResult AppListSyncableService::MergeDataAndStartSyncing(
    528     syncer::ModelType type,
    529     const syncer::SyncDataList& initial_sync_data,
    530     scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
    531     scoped_ptr<syncer::SyncErrorFactory> error_handler) {
    532   DCHECK(!sync_processor_.get());
    533   DCHECK(sync_processor.get());
    534   DCHECK(error_handler.get());
    535 
    536   sync_processor_ = sync_processor.Pass();
    537   sync_error_handler_ = error_handler.Pass();
    538   if (switches::IsFolderUIEnabled())
    539     model_->SetFoldersEnabled(true);
    540 
    541   syncer::SyncMergeResult result = syncer::SyncMergeResult(type);
    542   result.set_num_items_before_association(sync_items_.size());
    543   VLOG(1) << this << ": MergeDataAndStartSyncing: "
    544           << initial_sync_data.size();
    545 
    546   // Copy all sync items to |unsynced_items|.
    547   std::set<std::string> unsynced_items;
    548   for (SyncItemMap::const_iterator iter = sync_items_.begin();
    549        iter != sync_items_.end(); ++iter) {
    550     unsynced_items.insert(iter->first);
    551   }
    552 
    553   // Create SyncItem entries for initial_sync_data.
    554   size_t new_items = 0, updated_items = 0;
    555   for (syncer::SyncDataList::const_iterator iter = initial_sync_data.begin();
    556        iter != initial_sync_data.end(); ++iter) {
    557     const syncer::SyncData& data = *iter;
    558     const std::string& item_id = data.GetSpecifics().app_list().item_id();
    559     const sync_pb::AppListSpecifics& specifics = data.GetSpecifics().app_list();
    560     DVLOG(2) << this << "  Initial Sync Item: " << item_id
    561              << " Type: " << specifics.item_type();
    562     DCHECK_EQ(syncer::APP_LIST, data.GetDataType());
    563     if (ProcessSyncItemSpecifics(specifics))
    564       ++new_items;
    565     else
    566       ++updated_items;
    567     if (specifics.item_type() != sync_pb::AppListSpecifics::TYPE_FOLDER &&
    568         !IsUnRemovableDefaultApp(item_id) &&
    569         !AppIsOem(item_id) &&
    570         !AppIsDefault(extension_system_->extension_service(), item_id)) {
    571       VLOG(2) << "Syncing non-default item: " << item_id;
    572       first_app_list_sync_ = false;
    573     }
    574     unsynced_items.erase(item_id);
    575   }
    576   result.set_num_items_after_association(sync_items_.size());
    577   result.set_num_items_added(new_items);
    578   result.set_num_items_deleted(0);
    579   result.set_num_items_modified(updated_items);
    580 
    581   // Initial sync data has been processed, it is safe now to add new sync items.
    582   initial_sync_data_processed_ = true;
    583 
    584   // Send unsynced items. Does not affect |result|.
    585   syncer::SyncChangeList change_list;
    586   for (std::set<std::string>::iterator iter = unsynced_items.begin();
    587        iter != unsynced_items.end(); ++iter) {
    588     SyncItem* sync_item = FindSyncItem(*iter);
    589     // Sync can cause an item to change folders, causing an unsynced folder
    590     // item to be removed.
    591     if (!sync_item)
    592       continue;
    593     VLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
    594     change_list.push_back(SyncChange(FROM_HERE,  SyncChange::ACTION_ADD,
    595                                      GetSyncDataFromSyncItem(sync_item)));
    596   }
    597   sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
    598 
    599   // Adding items may have created folders without setting their positions
    600   // since we haven't started observing the item list yet. Resolve those.
    601   ResolveFolderPositions();
    602 
    603   // Start observing app list model changes.
    604   model_observer_.reset(new ModelObserver(this));
    605 
    606   return result;
    607 }
    608 
    609 void AppListSyncableService::StopSyncing(syncer::ModelType type) {
    610   DCHECK_EQ(type, syncer::APP_LIST);
    611 
    612   sync_processor_.reset();
    613   sync_error_handler_.reset();
    614   model_->SetFoldersEnabled(false);
    615 }
    616 
    617 syncer::SyncDataList AppListSyncableService::GetAllSyncData(
    618     syncer::ModelType type) const {
    619   DCHECK_EQ(syncer::APP_LIST, type);
    620 
    621   VLOG(1) << this << ": GetAllSyncData: " << sync_items_.size();
    622   syncer::SyncDataList list;
    623   for (SyncItemMap::const_iterator iter = sync_items_.begin();
    624        iter != sync_items_.end(); ++iter) {
    625     VLOG(2) << this << " -> SYNC: " << iter->second->ToString();
    626     list.push_back(GetSyncDataFromSyncItem(iter->second));
    627   }
    628   return list;
    629 }
    630 
    631 syncer::SyncError AppListSyncableService::ProcessSyncChanges(
    632     const tracked_objects::Location& from_here,
    633     const syncer::SyncChangeList& change_list) {
    634   if (!sync_processor_.get()) {
    635     return syncer::SyncError(FROM_HERE,
    636                              syncer::SyncError::DATATYPE_ERROR,
    637                              "App List syncable service is not started.",
    638                              syncer::APP_LIST);
    639   }
    640 
    641   // Don't observe the model while processing incoming sync changes.
    642   model_observer_.reset();
    643 
    644   VLOG(1) << this << ": ProcessSyncChanges: " << change_list.size();
    645   for (syncer::SyncChangeList::const_iterator iter = change_list.begin();
    646        iter != change_list.end(); ++iter) {
    647     const SyncChange& change = *iter;
    648     VLOG(2) << this << "  Change: "
    649             << change.sync_data().GetSpecifics().app_list().item_id()
    650             << " (" << change.change_type() << ")";
    651     if (change.change_type() == SyncChange::ACTION_ADD ||
    652         change.change_type() == SyncChange::ACTION_UPDATE) {
    653       ProcessSyncItemSpecifics(change.sync_data().GetSpecifics().app_list());
    654     } else if (change.change_type() == SyncChange::ACTION_DELETE) {
    655       DeleteSyncItemSpecifics(change.sync_data().GetSpecifics().app_list());
    656     } else {
    657       LOG(ERROR) << "Invalid sync change";
    658     }
    659   }
    660 
    661   // Continue observing app list model changes.
    662   model_observer_.reset(new ModelObserver(this));
    663 
    664   return syncer::SyncError();
    665 }
    666 
    667 // AppListSyncableService private
    668 
    669 bool AppListSyncableService::ProcessSyncItemSpecifics(
    670     const sync_pb::AppListSpecifics& specifics) {
    671   const std::string& item_id = specifics.item_id();
    672   if (item_id.empty()) {
    673     LOG(ERROR) << "AppList item with empty ID";
    674     return false;
    675   }
    676   SyncItem* sync_item = FindSyncItem(item_id);
    677   if (sync_item) {
    678     // If an item of the same type exists, update it.
    679     if (sync_item->item_type == specifics.item_type()) {
    680       UpdateSyncItemFromSync(specifics, sync_item);
    681       ProcessExistingSyncItem(sync_item);
    682       VLOG(2) << this << " <- SYNC UPDATE: " << sync_item->ToString();
    683       return false;
    684     }
    685     // Otherwise, one of the entries should be TYPE_REMOVE_DEFAULT_APP.
    686     if (sync_item->item_type !=
    687         sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP &&
    688         specifics.item_type() !=
    689         sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
    690       LOG(ERROR) << "Synced item type: " << specifics.item_type()
    691                  << " != existing sync item type: " << sync_item->item_type
    692                  << " Deleting item from model!";
    693       model_->DeleteItem(item_id);
    694     }
    695     VLOG(2) << this << " - ProcessSyncItem: Delete existing entry: "
    696             << sync_item->ToString();
    697     delete sync_item;
    698     sync_items_.erase(item_id);
    699   }
    700 
    701   sync_item = CreateSyncItem(item_id, specifics.item_type());
    702   UpdateSyncItemFromSync(specifics, sync_item);
    703   ProcessNewSyncItem(sync_item);
    704   VLOG(2) << this << " <- SYNC ADD: " << sync_item->ToString();
    705   return true;
    706 }
    707 
    708 void AppListSyncableService::ProcessNewSyncItem(SyncItem* sync_item) {
    709   VLOG(2) << "ProcessNewSyncItem: " << sync_item->ToString();
    710   switch (sync_item->item_type) {
    711     case sync_pb::AppListSpecifics::TYPE_APP: {
    712       // New apps are added through ExtensionAppModelBuilder.
    713       // TODO(stevenjb): Determine how to handle app items in sync that
    714       // are not installed (e.g. default / OEM apps).
    715       return;
    716     }
    717     case sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP: {
    718       VLOG(1) << this << ": Uninstall: " << sync_item->ToString();
    719       UninstallExtension(extension_system_->extension_service(),
    720                          sync_item->item_id);
    721       return;
    722     }
    723     case sync_pb::AppListSpecifics::TYPE_FOLDER: {
    724       AppListItem* app_item = model_->FindItem(sync_item->item_id);
    725       if (!app_item)
    726         return;  // Don't create new folders here, the model will do that.
    727       UpdateAppItemFromSyncItem(sync_item, app_item);
    728       return;
    729     }
    730     case sync_pb::AppListSpecifics::TYPE_URL: {
    731       // TODO(stevenjb): Implement
    732       LOG(WARNING) << "TYPE_URL not supported";
    733       return;
    734     }
    735   }
    736   NOTREACHED() << "Unrecognized sync item type: " << sync_item->ToString();
    737 }
    738 
    739 void AppListSyncableService::ProcessExistingSyncItem(SyncItem* sync_item) {
    740   if (sync_item->item_type ==
    741       sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
    742     return;
    743   }
    744   VLOG(2) << "ProcessExistingSyncItem: " << sync_item->ToString();
    745   AppListItem* app_item = model_->FindItem(sync_item->item_id);
    746   DVLOG(2) << " AppItem: " << app_item->ToDebugString();
    747   if (!app_item) {
    748     LOG(ERROR) << "Item not found in model: " << sync_item->ToString();
    749     return;
    750   }
    751   // This is the only place where sync can cause an item to change folders.
    752   if (app_list::switches::IsFolderUIEnabled() &&
    753       app_item->folder_id() != sync_item->parent_id &&
    754       !AppIsOem(app_item->id())) {
    755     VLOG(2) << " Moving Item To Folder: " << sync_item->parent_id;
    756     model_->MoveItemToFolder(app_item, sync_item->parent_id);
    757   }
    758   UpdateAppItemFromSyncItem(sync_item, app_item);
    759 }
    760 
    761 void AppListSyncableService::UpdateAppItemFromSyncItem(
    762     const AppListSyncableService::SyncItem* sync_item,
    763     AppListItem* app_item) {
    764   VLOG(2) << this << " UpdateAppItemFromSyncItem: " << sync_item->ToString();
    765   if (!app_item->position().Equals(sync_item->item_ordinal))
    766     model_->SetItemPosition(app_item, sync_item->item_ordinal);
    767   // Only update the item name if it is a Folder or the name is empty.
    768   if (sync_item->item_name != app_item->name() &&
    769       sync_item->item_id != kOemFolderId &&
    770       (app_item->GetItemType() == AppListFolderItem::kItemType ||
    771        app_item->name().empty())) {
    772     model_->SetItemName(app_item, sync_item->item_name);
    773   }
    774 }
    775 
    776 bool AppListSyncableService::SyncStarted() {
    777   if (sync_processor_.get())
    778     return true;
    779   if (flare_.is_null()) {
    780     VLOG(1) << this << ": SyncStarted: Flare.";
    781     flare_ = sync_start_util::GetFlareForSyncableService(profile_->GetPath());
    782     flare_.Run(syncer::APP_LIST);
    783   }
    784   return false;
    785 }
    786 
    787 void AppListSyncableService::SendSyncChange(
    788     SyncItem* sync_item,
    789     SyncChange::SyncChangeType sync_change_type) {
    790   if (!SyncStarted()) {
    791     DVLOG(2) << this << " - SendSyncChange: SYNC NOT STARTED: "
    792              << sync_item->ToString();
    793     return;
    794   }
    795   if (!initial_sync_data_processed_ &&
    796       sync_change_type == SyncChange::ACTION_ADD) {
    797     // This can occur if an initial item is created before its folder item.
    798     // A sync item should already exist for the folder, so we do not want to
    799     // send an ADD event, since that would trigger a CHECK in the sync code.
    800     DCHECK(sync_item->item_type == sync_pb::AppListSpecifics::TYPE_FOLDER);
    801     DVLOG(2) << this << " - SendSyncChange: ADD before initial data processed: "
    802              << sync_item->ToString();
    803     return;
    804   }
    805   if (sync_change_type == SyncChange::ACTION_ADD)
    806     VLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
    807   else
    808     VLOG(2) << this << " -> SYNC UPDATE: " << sync_item->ToString();
    809   SyncChange sync_change(FROM_HERE, sync_change_type,
    810                          GetSyncDataFromSyncItem(sync_item));
    811   sync_processor_->ProcessSyncChanges(
    812       FROM_HERE, syncer::SyncChangeList(1, sync_change));
    813 }
    814 
    815 AppListSyncableService::SyncItem*
    816 AppListSyncableService::FindSyncItem(const std::string& item_id) {
    817   SyncItemMap::iterator iter = sync_items_.find(item_id);
    818   if (iter == sync_items_.end())
    819     return NULL;
    820   return iter->second;
    821 }
    822 
    823 AppListSyncableService::SyncItem*
    824 AppListSyncableService::CreateSyncItem(
    825     const std::string& item_id,
    826     sync_pb::AppListSpecifics::AppListItemType item_type) {
    827   DCHECK(!ContainsKey(sync_items_, item_id));
    828   SyncItem* sync_item = new SyncItem(item_id, item_type);
    829   sync_items_[item_id] = sync_item;
    830   return sync_item;
    831 }
    832 
    833 void AppListSyncableService::DeleteSyncItemSpecifics(
    834     const sync_pb::AppListSpecifics& specifics) {
    835   const std::string& item_id = specifics.item_id();
    836   if (item_id.empty()) {
    837     LOG(ERROR) << "Delete AppList item with empty ID";
    838     return;
    839   }
    840   VLOG(2) << this << ": DeleteSyncItemSpecifics: " << item_id.substr(0, 8);
    841   SyncItemMap::iterator iter = sync_items_.find(item_id);
    842   if (iter == sync_items_.end())
    843     return;
    844   sync_pb::AppListSpecifics::AppListItemType item_type =
    845       iter->second->item_type;
    846   VLOG(2) << this << " <- SYNC DELETE: " << iter->second->ToString();
    847   delete iter->second;
    848   sync_items_.erase(iter);
    849   // Only delete apps from the model. Folders will be deleted when all
    850   // children have been deleted.
    851   if (item_type == sync_pb::AppListSpecifics::TYPE_APP)
    852     model_->DeleteItem(item_id);
    853 }
    854 
    855 std::string AppListSyncableService::FindOrCreateOemFolder() {
    856   AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
    857   if (!oem_folder) {
    858     scoped_ptr<AppListFolderItem> new_folder(new AppListFolderItem(
    859         kOemFolderId, AppListFolderItem::FOLDER_TYPE_OEM));
    860     oem_folder = static_cast<AppListFolderItem*>(
    861         model_->AddItem(new_folder.PassAs<app_list::AppListItem>()));
    862     SyncItem* oem_sync_item = FindSyncItem(kOemFolderId);
    863     if (oem_sync_item) {
    864       VLOG(1) << "Creating OEM folder from existing sync item: "
    865                << oem_sync_item->item_ordinal.ToDebugString();
    866       model_->SetItemPosition(oem_folder, oem_sync_item->item_ordinal);
    867     } else {
    868       model_->SetItemPosition(oem_folder, GetOemFolderPos());
    869       // Do not create a sync item for the OEM folder here, do it in
    870       // ResolveFolderPositions() when the item position is finalized.
    871     }
    872   }
    873   model_->SetItemName(oem_folder, oem_folder_name_);
    874   return oem_folder->id();
    875 }
    876 
    877 syncer::StringOrdinal AppListSyncableService::GetOemFolderPos() {
    878   VLOG(1) << "GetOemFolderPos: " << first_app_list_sync_;
    879   if (!first_app_list_sync_) {
    880     VLOG(1) << "Sync items exist, placing OEM folder at end.";
    881     syncer::StringOrdinal last;
    882     for (SyncItemMap::iterator iter = sync_items_.begin();
    883          iter != sync_items_.end(); ++iter) {
    884       SyncItem* sync_item = iter->second;
    885       if (!last.IsValid() || sync_item->item_ordinal.GreaterThan(last))
    886         last = sync_item->item_ordinal;
    887     }
    888     return last.CreateAfter();
    889   }
    890 
    891   // Place the OEM folder just after the web store, which should always be
    892   // followed by a pre-installed app (e.g. Search), so the poosition should be
    893   // stable. TODO(stevenjb): consider explicitly setting the OEM folder location
    894   // along with the name in ServicesCustomizationDocument::SetOemFolderName().
    895   AppListItemList* item_list = model_->top_level_item_list();
    896   if (item_list->item_count() == 0)
    897     return syncer::StringOrdinal();
    898 
    899   size_t oem_index = 0;
    900   for (; oem_index < item_list->item_count() - 1; ++oem_index) {
    901     AppListItem* cur_item = item_list->item_at(oem_index);
    902     if (cur_item->id() == extensions::kWebStoreAppId)
    903       break;
    904   }
    905   syncer::StringOrdinal oem_ordinal;
    906   AppListItem* prev = item_list->item_at(oem_index);
    907   if (oem_index + 1 < item_list->item_count()) {
    908     AppListItem* next = item_list->item_at(oem_index + 1);
    909     oem_ordinal = prev->position().CreateBetween(next->position());
    910   } else {
    911     oem_ordinal = prev->position().CreateAfter();
    912   }
    913   VLOG(1) << "Placing OEM Folder at: " << oem_index
    914           << " position: " << oem_ordinal.ToDebugString();
    915   return oem_ordinal;
    916 }
    917 
    918 bool AppListSyncableService::AppIsOem(const std::string& id) {
    919   if (!extension_system_->extension_service())
    920     return false;
    921   const extensions::Extension* extension =
    922       extension_system_->extension_service()->GetExtensionById(id, true);
    923   return extension && extension->was_installed_by_oem();
    924 }
    925 
    926 std::string AppListSyncableService::SyncItem::ToString() const {
    927   std::string res = item_id.substr(0, 8);
    928   if (item_type == sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
    929     res += " { RemoveDefault }";
    930   } else {
    931     res += " { " + item_name + " }";
    932     res += " [" + item_ordinal.ToDebugString() + "]";
    933     if (!parent_id.empty())
    934       res += " <" + parent_id.substr(0, 8) + ">";
    935   }
    936   return res;
    937 }
    938 
    939 }  // namespace app_list
    940