app_list_syncable_service.cc revision e5d81f57cb97b3b6b7fccc9c5610d21eb81db09d
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
31using syncer::SyncChange;
32
33namespace app_list {
34
35namespace {
36
37const char kOemFolderId[] = "ddb1da55-d478-4243-8642-56d3041f0263";
38
39bool SyncAppListEnabled() {
40  return !CommandLine::ForCurrentProcess()->HasSwitch(
41      ::switches::kDisableSyncAppList);
42}
43
44void 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
56bool 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
78void 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
91syncer::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
100bool AppIsDefault(ExtensionService* service, const std::string& id) {
101  return service && extensions::ExtensionPrefs::Get(service->profile())
102                        ->WasInstalledByDefault(id);
103}
104
105void UninstallExtension(ExtensionService* service, const std::string& id) {
106  if (service && service->GetInstalledExtension(id))
107    service->UninstallExtension(id, false, NULL);
108}
109
110bool 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
128AppListSyncableService::SyncItem::SyncItem(
129    const std::string& id,
130    sync_pb::AppListSpecifics::AppListItemType type)
131    : item_id(id),
132      item_type(type) {
133}
134
135AppListSyncableService::SyncItem::~SyncItem() {
136}
137
138// AppListSyncableService::ModelObserver
139
140class 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
177AppListSyncableService::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
205AppListSyncableService::~AppListSyncableService() {
206  // Remove observers.
207  model_observer_.reset();
208
209  STLDeleteContainerPairSecondPointers(sync_items_.begin(), sync_items_.end());
210}
211
212void 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
235void 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
245const AppListSyncableService::SyncItem*
246AppListSyncableService::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
253void 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
260void 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 (app_list::switches::IsFolderUIEnabled()) {
267    if (AppIsOem(app_item->id())) {
268      folder_id = FindOrCreateOemFolder();
269      DVLOG(1) << this << ": AddItem to OEM folder: " << sync_item->ToString();
270    } else {
271      folder_id = sync_item->parent_id;
272      DVLOG(1) << this << ": AddItem: " << sync_item->ToString()
273               << " Folder: '" << folder_id << "'";
274    }
275  }
276  DVLOG(1) << this << ": AddItem: " << sync_item->ToString()
277           << "Folder: '" << folder_id << "'";
278  model_->AddItemToFolder(app_item.Pass(), folder_id);
279}
280
281AppListSyncableService::SyncItem* AppListSyncableService::FindOrAddSyncItem(
282    AppListItem* app_item) {
283  const std::string& item_id = app_item->id();
284  if (item_id.empty()) {
285    LOG(ERROR) << "AppListItem item with empty ID";
286    return NULL;
287  }
288  SyncItem* sync_item = FindSyncItem(item_id);
289  if (sync_item) {
290    // If there is an existing, non-REMOVE_DEFAULT entry, return it.
291    if (sync_item->item_type !=
292        sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
293      DVLOG(2) << this << ": AddItem already exists: " << sync_item->ToString();
294      return sync_item;
295    }
296
297    if (RemoveDefaultApp(app_item, sync_item))
298      return NULL;
299
300    // Fall through. The REMOVE_DEFAULT_APP entry has been deleted, now a new
301    // App entry can be added.
302  }
303
304  return CreateSyncItemFromAppItem(app_item);
305}
306
307AppListSyncableService::SyncItem*
308AppListSyncableService::CreateSyncItemFromAppItem(AppListItem* app_item) {
309  sync_pb::AppListSpecifics::AppListItemType type;
310  if (!GetAppListItemType(app_item, &type))
311    return NULL;
312  SyncItem* sync_item = CreateSyncItem(app_item->id(), type);
313  UpdateSyncItemFromAppItem(app_item, sync_item);
314  SendSyncChange(sync_item, SyncChange::ACTION_ADD);
315  return sync_item;
316}
317
318void AppListSyncableService::AddOrUpdateFromSyncItem(AppListItem* app_item) {
319  SyncItem* sync_item = FindSyncItem(app_item->id());
320  if (sync_item) {
321    UpdateAppItemFromSyncItem(sync_item, app_item);
322    return;
323  }
324  CreateSyncItemFromAppItem(app_item);
325}
326
327bool AppListSyncableService::RemoveDefaultApp(AppListItem* item,
328                                              SyncItem* sync_item) {
329  CHECK_EQ(sync_item->item_type,
330           sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP);
331
332  // If there is an existing REMOVE_DEFAULT_APP entry, and the app is
333  // installed as a Default app, uninstall the app instead of adding it.
334  if (sync_item->item_type == sync_pb::AppListSpecifics::TYPE_APP &&
335      AppIsDefault(extension_system_->extension_service(), item->id())) {
336    DVLOG(1) << this << ": HandleDefaultApp: Uninstall: "
337             << sync_item->ToString();
338    UninstallExtension(extension_system_->extension_service(), item->id());
339    return true;
340  }
341
342  // Otherwise, we are adding the app as a non-default app (i.e. an app that
343  // was installed by Default and removed is getting installed explicitly by
344  // the user), so delete the REMOVE_DEFAULT_APP.
345  DeleteSyncItem(sync_item);
346  return false;
347}
348
349void AppListSyncableService::DeleteSyncItem(SyncItem* sync_item) {
350  if (SyncStarted()) {
351    DVLOG(2) << this << " -> SYNC DELETE: " << sync_item->ToString();
352    SyncChange sync_change(FROM_HERE, SyncChange::ACTION_DELETE,
353                           GetSyncDataFromSyncItem(sync_item));
354    sync_processor_->ProcessSyncChanges(
355        FROM_HERE, syncer::SyncChangeList(1, sync_change));
356  }
357  std::string item_id = sync_item->item_id;
358  delete sync_item;
359  sync_items_.erase(item_id);
360}
361
362void AppListSyncableService::UpdateSyncItem(AppListItem* app_item) {
363  SyncItem* sync_item = FindSyncItem(app_item->id());
364  if (!sync_item) {
365    LOG(ERROR) << "UpdateItem: no sync item: " << app_item->id();
366    return;
367  }
368  bool changed = UpdateSyncItemFromAppItem(app_item, sync_item);
369  if (!changed) {
370    DVLOG(2) << this << " - Update: SYNC NO CHANGE: " << sync_item->ToString();
371    return;
372  }
373  SendSyncChange(sync_item, SyncChange::ACTION_UPDATE);
374}
375
376void AppListSyncableService::RemoveItem(const std::string& id) {
377  RemoveSyncItem(id);
378  model_->DeleteItem(id);
379  PruneEmptySyncFolders();
380}
381
382void AppListSyncableService::UpdateItem(AppListItem* app_item) {
383  // Check to see if the item needs to be moved to/from the OEM folder.
384  if (!app_list::switches::IsFolderUIEnabled())
385    return;
386  bool is_oem = AppIsOem(app_item->id());
387  if (!is_oem && app_item->folder_id() == kOemFolderId)
388    model_->MoveItemToFolder(app_item, "");
389  else if (is_oem && app_item->folder_id() != kOemFolderId)
390    model_->MoveItemToFolder(app_item, kOemFolderId);
391}
392
393void AppListSyncableService::RemoveSyncItem(const std::string& id) {
394  DVLOG(2) << this << ": RemoveSyncItem: " << id.substr(0, 8);
395  SyncItemMap::iterator iter = sync_items_.find(id);
396  if (iter == sync_items_.end()) {
397    DVLOG(2) << this << " : RemoveSyncItem: No Item.";
398    return;
399  }
400
401  // Check for existing RemoveDefault sync item.
402  SyncItem* sync_item = iter->second;
403  sync_pb::AppListSpecifics::AppListItemType type = sync_item->item_type;
404  if (type == sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
405    // RemoveDefault item exists, just return.
406    DVLOG(2) << this << " : RemoveDefault Item exists.";
407    return;
408  }
409
410  if (type == sync_pb::AppListSpecifics::TYPE_APP &&
411      AppIsDefault(extension_system_->extension_service(), id)) {
412    // This is a Default app; update the entry to a REMOVE_DEFAULT entry. This
413    // will overwrite any existing entry for the item.
414    DVLOG(2) << this << " -> SYNC UPDATE: REMOVE_DEFAULT: "
415             << sync_item->item_id;
416    sync_item->item_type = sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP;
417    SendSyncChange(sync_item, SyncChange::ACTION_UPDATE);
418    return;
419  }
420
421  DeleteSyncItem(sync_item);
422}
423
424void AppListSyncableService::ResolveFolderPositions(bool move_oem_to_end) {
425  if (!app_list::switches::IsFolderUIEnabled())
426    return;
427
428  for (SyncItemMap::iterator iter = sync_items_.begin();
429       iter != sync_items_.end(); ++iter) {
430    SyncItem* sync_item = iter->second;
431    if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
432      continue;
433    AppListItem* app_item = model_->FindItem(sync_item->item_id);
434    if (!app_item)
435      continue;
436    if (move_oem_to_end && app_item->id() == kOemFolderId) {
437      // Move the OEM folder to the end.
438      model_->SetItemPosition(app_item, syncer::StringOrdinal());
439    }
440    UpdateAppItemFromSyncItem(sync_item, app_item);
441  }
442}
443
444void AppListSyncableService::PruneEmptySyncFolders() {
445  if (!app_list::switches::IsFolderUIEnabled())
446    return;
447
448  std::set<std::string> parent_ids;
449  for (SyncItemMap::iterator iter = sync_items_.begin();
450       iter != sync_items_.end(); ++iter) {
451    parent_ids.insert(iter->second->parent_id);
452  }
453  for (SyncItemMap::iterator iter = sync_items_.begin();
454       iter != sync_items_.end(); ) {
455    SyncItem* sync_item = (iter++)->second;
456    if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
457      continue;
458    if (!ContainsKey(parent_ids, sync_item->item_id))
459      DeleteSyncItem(sync_item);
460  }
461}
462
463// AppListSyncableService syncer::SyncableService
464
465syncer::SyncMergeResult AppListSyncableService::MergeDataAndStartSyncing(
466    syncer::ModelType type,
467    const syncer::SyncDataList& initial_sync_data,
468    scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
469    scoped_ptr<syncer::SyncErrorFactory> error_handler) {
470  DCHECK(!sync_processor_.get());
471  DCHECK(sync_processor.get());
472  DCHECK(error_handler.get());
473
474  sync_processor_ = sync_processor.Pass();
475  sync_error_handler_ = error_handler.Pass();
476
477  syncer::SyncMergeResult result = syncer::SyncMergeResult(type);
478  result.set_num_items_before_association(sync_items_.size());
479  DVLOG(1) << this << ": MergeDataAndStartSyncing: "
480           << initial_sync_data.size();
481
482  // Copy all sync items to |unsynced_items|.
483  std::set<std::string> unsynced_items;
484  for (SyncItemMap::const_iterator iter = sync_items_.begin();
485       iter != sync_items_.end(); ++iter) {
486    unsynced_items.insert(iter->first);
487  }
488
489  // Create SyncItem entries for initial_sync_data.
490  size_t new_items = 0, updated_items = 0;
491  bool oem_folder_is_synced = false;
492  for (syncer::SyncDataList::const_iterator iter = initial_sync_data.begin();
493       iter != initial_sync_data.end(); ++iter) {
494    const syncer::SyncData& data = *iter;
495    const std::string& item_id = data.GetSpecifics().app_list().item_id();
496    DVLOG(2) << this << "  Initial Sync Item: " << item_id
497             << " Type: " << data.GetSpecifics().app_list().item_type();
498    DCHECK_EQ(syncer::APP_LIST, data.GetDataType());
499    if (ProcessSyncItemSpecifics(data.GetSpecifics().app_list()))
500      ++new_items;
501    else
502      ++updated_items;
503    unsynced_items.erase(item_id);
504    if (item_id == kOemFolderId)
505      oem_folder_is_synced = true;
506  }
507
508  result.set_num_items_after_association(sync_items_.size());
509  result.set_num_items_added(new_items);
510  result.set_num_items_deleted(0);
511  result.set_num_items_modified(updated_items);
512
513  // Send unsynced items. Does not affect |result|.
514  syncer::SyncChangeList change_list;
515  for (std::set<std::string>::iterator iter = unsynced_items.begin();
516       iter != unsynced_items.end(); ++iter) {
517    SyncItem* sync_item = FindSyncItem(*iter);
518    DVLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
519    change_list.push_back(SyncChange(FROM_HERE,  SyncChange::ACTION_ADD,
520                                     GetSyncDataFromSyncItem(sync_item)));
521  }
522  sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
523
524  // Adding items may have created folders without setting their positions
525  // since we haven't started observing the item list yet. Resolve those.
526  // Also ensure the OEM folder is at the end if its position hasn't been set.
527  bool move_oem_to_end = !oem_folder_is_synced;
528  ResolveFolderPositions(move_oem_to_end);
529
530  // Start observing app list model changes.
531  model_observer_.reset(new ModelObserver(this));
532
533  return result;
534}
535
536void AppListSyncableService::StopSyncing(syncer::ModelType type) {
537  DCHECK_EQ(type, syncer::APP_LIST);
538
539  sync_processor_.reset();
540  sync_error_handler_.reset();
541}
542
543syncer::SyncDataList AppListSyncableService::GetAllSyncData(
544    syncer::ModelType type) const {
545  DCHECK_EQ(syncer::APP_LIST, type);
546
547  DVLOG(1) << this << ": GetAllSyncData: " << sync_items_.size();
548  syncer::SyncDataList list;
549  for (SyncItemMap::const_iterator iter = sync_items_.begin();
550       iter != sync_items_.end(); ++iter) {
551    DVLOG(2) << this << " -> SYNC: " << iter->second->ToString();
552    list.push_back(GetSyncDataFromSyncItem(iter->second));
553  }
554  return list;
555}
556
557syncer::SyncError AppListSyncableService::ProcessSyncChanges(
558    const tracked_objects::Location& from_here,
559    const syncer::SyncChangeList& change_list) {
560  if (!sync_processor_.get()) {
561    return syncer::SyncError(FROM_HERE,
562                             syncer::SyncError::DATATYPE_ERROR,
563                             "App List syncable service is not started.",
564                             syncer::APP_LIST);
565  }
566
567  // Don't observe the model while processing incoming sync changes.
568  model_observer_.reset();
569
570  DVLOG(1) << this << ": ProcessSyncChanges: " << change_list.size();
571  for (syncer::SyncChangeList::const_iterator iter = change_list.begin();
572       iter != change_list.end(); ++iter) {
573    const SyncChange& change = *iter;
574    DVLOG(2) << this << "  Change: "
575             << change.sync_data().GetSpecifics().app_list().item_id()
576             << " (" << change.change_type() << ")";
577    if (change.change_type() == SyncChange::ACTION_ADD ||
578        change.change_type() == SyncChange::ACTION_UPDATE) {
579      ProcessSyncItemSpecifics(change.sync_data().GetSpecifics().app_list());
580    } else if (change.change_type() == SyncChange::ACTION_DELETE) {
581      DeleteSyncItemSpecifics(change.sync_data().GetSpecifics().app_list());
582    } else {
583      LOG(ERROR) << "Invalid sync change";
584    }
585  }
586
587  // Continue observing app list model changes.
588  model_observer_.reset(new ModelObserver(this));
589
590  return syncer::SyncError();
591}
592
593// AppListSyncableService private
594
595bool AppListSyncableService::ProcessSyncItemSpecifics(
596    const sync_pb::AppListSpecifics& specifics) {
597  const std::string& item_id = specifics.item_id();
598  if (item_id.empty()) {
599    LOG(ERROR) << "AppList item with empty ID";
600    return false;
601  }
602  SyncItem* sync_item = FindSyncItem(item_id);
603  if (sync_item) {
604    // If an item of the same type exists, update it.
605    if (sync_item->item_type == specifics.item_type()) {
606      UpdateSyncItemFromSync(specifics, sync_item);
607      ProcessExistingSyncItem(sync_item);
608      DVLOG(2) << this << " <- SYNC UPDATE: " << sync_item->ToString();
609      return false;
610    }
611    // Otherwise, one of the entries should be TYPE_REMOVE_DEFAULT_APP.
612    if (sync_item->item_type !=
613        sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP &&
614        specifics.item_type() !=
615        sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
616      LOG(ERROR) << "Synced item type: " << specifics.item_type()
617                 << " != existing sync item type: " << sync_item->item_type
618                 << " Deleting item from model!";
619      model_->DeleteItem(item_id);
620    }
621    DVLOG(2) << this << " - ProcessSyncItem: Delete existing entry: "
622             << sync_item->ToString();
623    delete sync_item;
624    sync_items_.erase(item_id);
625  }
626
627  sync_item = CreateSyncItem(item_id, specifics.item_type());
628  UpdateSyncItemFromSync(specifics, sync_item);
629  ProcessNewSyncItem(sync_item);
630  DVLOG(2) << this << " <- SYNC ADD: " << sync_item->ToString();
631  return true;
632}
633
634void AppListSyncableService::ProcessNewSyncItem(SyncItem* sync_item) {
635  DVLOG(2) << "ProcessNewSyncItem: " << sync_item->ToString();
636  switch (sync_item->item_type) {
637    case sync_pb::AppListSpecifics::TYPE_APP: {
638      // New apps are added through ExtensionAppModelBuilder.
639      // TODO(stevenjb): Determine how to handle app items in sync that
640      // are not installed (e.g. default / OEM apps).
641      return;
642    }
643    case sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP: {
644      DVLOG(1) << this << ": Uninstall: " << sync_item->ToString();
645      UninstallExtension(extension_system_->extension_service(),
646                         sync_item->item_id);
647      return;
648    }
649    case sync_pb::AppListSpecifics::TYPE_FOLDER: {
650      AppListItem* app_item = model_->FindItem(sync_item->item_id);
651      if (!app_item)
652        return;  // Don't create new folders here, the model will do that.
653      UpdateAppItemFromSyncItem(sync_item, app_item);
654      return;
655    }
656    case sync_pb::AppListSpecifics::TYPE_URL: {
657      // TODO(stevenjb): Implement
658      LOG(WARNING) << "TYPE_URL not supported";
659      return;
660    }
661  }
662  NOTREACHED() << "Unrecognized sync item type: " << sync_item->ToString();
663}
664
665void AppListSyncableService::ProcessExistingSyncItem(SyncItem* sync_item) {
666  if (sync_item->item_type ==
667      sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
668    return;
669  }
670  DVLOG(2) << "ProcessExistingSyncItem: " << sync_item->ToString();
671  AppListItem* app_item = model_->FindItem(sync_item->item_id);
672  DVLOG(2) << " AppItem: " << app_item->ToDebugString();
673  if (!app_item) {
674    LOG(ERROR) << "Item not found in model: " << sync_item->ToString();
675    return;
676  }
677  // This is the only place where sync can cause an item to change folders.
678  if (app_list::switches::IsFolderUIEnabled() &&
679      app_item->folder_id() != sync_item->parent_id &&
680      !AppIsOem(app_item->id())) {
681    DVLOG(2) << " Moving Item To Folder: " << sync_item->parent_id;
682    model_->MoveItemToFolder(app_item, sync_item->parent_id);
683  }
684  UpdateAppItemFromSyncItem(sync_item, app_item);
685}
686
687void AppListSyncableService::UpdateAppItemFromSyncItem(
688    const AppListSyncableService::SyncItem* sync_item,
689    AppListItem* app_item) {
690  if (!app_item->position().Equals(sync_item->item_ordinal))
691    model_->SetItemPosition(app_item, sync_item->item_ordinal);
692  // Only update the item name if it is a Folder or the name is empty.
693  if (sync_item->item_name != app_item->name() &&
694      sync_item->item_id != kOemFolderId &&
695      (app_item->GetItemType() == AppListFolderItem::kItemType ||
696       app_item->name().empty())) {
697    model_->SetItemName(app_item, sync_item->item_name);
698  }
699}
700
701bool AppListSyncableService::SyncStarted() {
702  if (sync_processor_.get())
703    return true;
704  if (flare_.is_null()) {
705    DVLOG(2) << this << ": SyncStarted: Flare.";
706    flare_ = sync_start_util::GetFlareForSyncableService(profile_->GetPath());
707    flare_.Run(syncer::APP_LIST);
708  }
709  return false;
710}
711
712void AppListSyncableService::SendSyncChange(
713    SyncItem* sync_item,
714    SyncChange::SyncChangeType sync_change_type) {
715  if (!SyncStarted()) {
716    DVLOG(2) << this << " - SendSyncChange: SYNC NOT STARTED: "
717             << sync_item->ToString();
718    return;
719  }
720  if (sync_change_type == SyncChange::ACTION_ADD)
721    DVLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
722  else
723    DVLOG(2) << this << " -> SYNC UPDATE: " << sync_item->ToString();
724  SyncChange sync_change(FROM_HERE, sync_change_type,
725                         GetSyncDataFromSyncItem(sync_item));
726  sync_processor_->ProcessSyncChanges(
727      FROM_HERE, syncer::SyncChangeList(1, sync_change));
728}
729
730AppListSyncableService::SyncItem*
731AppListSyncableService::FindSyncItem(const std::string& item_id) {
732  SyncItemMap::iterator iter = sync_items_.find(item_id);
733  if (iter == sync_items_.end())
734    return NULL;
735  return iter->second;
736}
737
738AppListSyncableService::SyncItem*
739AppListSyncableService::CreateSyncItem(
740    const std::string& item_id,
741    sync_pb::AppListSpecifics::AppListItemType item_type) {
742  DCHECK(!ContainsKey(sync_items_, item_id));
743  SyncItem* sync_item = new SyncItem(item_id, item_type);
744  sync_items_[item_id] = sync_item;
745  return sync_item;
746}
747
748void AppListSyncableService::DeleteSyncItemSpecifics(
749    const sync_pb::AppListSpecifics& specifics) {
750  const std::string& item_id = specifics.item_id();
751  if (item_id.empty()) {
752    LOG(ERROR) << "Delete AppList item with empty ID";
753    return;
754  }
755  DVLOG(2) << this << ": DeleteSyncItemSpecifics: " << item_id.substr(0, 8);
756  SyncItemMap::iterator iter = sync_items_.find(item_id);
757  if (iter == sync_items_.end())
758    return;
759  sync_pb::AppListSpecifics::AppListItemType item_type =
760      iter->second->item_type;
761  DVLOG(2) << this << " <- SYNC DELETE: " << iter->second->ToString();
762  delete iter->second;
763  sync_items_.erase(iter);
764  // Only delete apps from the model. Folders will be deleted when all
765  // children have been deleted.
766  if (item_type == sync_pb::AppListSpecifics::TYPE_APP)
767    model_->DeleteItem(item_id);
768}
769
770std::string AppListSyncableService::FindOrCreateOemFolder() {
771  AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
772  if (!oem_folder) {
773    scoped_ptr<AppListFolderItem> new_folder(new AppListFolderItem(
774        kOemFolderId, AppListFolderItem::FOLDER_TYPE_OEM));
775    oem_folder = static_cast<AppListFolderItem*>(
776        model_->AddItem(new_folder.PassAs<app_list::AppListItem>()));
777  }
778  model_->SetItemName(oem_folder, oem_folder_name_);
779  return oem_folder->id();
780}
781
782bool AppListSyncableService::AppIsOem(const std::string& id) {
783  if (!extension_system_->extension_service())
784    return false;
785  const extensions::Extension* extension =
786      extension_system_->extension_service()->GetExtensionById(id, true);
787  return extension && extension->was_installed_by_oem();
788}
789
790std::string AppListSyncableService::SyncItem::ToString() const {
791  std::string res = item_id.substr(0, 8);
792  if (item_type == sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
793    res += " { RemoveDefault }";
794  } else {
795    res += " { " + item_name + " }";
796    res += " [" + item_ordinal.ToDebugString() + "]";
797    if (!parent_id.empty())
798      res += " <" + parent_id.substr(0, 8) + ">";
799  }
800  return res;
801}
802
803}  // namespace app_list
804