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