Upstream version 9.37.195.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / app_list / app_list_syncable_service.cc
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