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