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