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