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