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.
5 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
7 #include "base/command_line.h"
8 #include "chrome/browser/apps/drive/drive_app_provider.h"
9 #include "chrome/browser/chrome_notification_types.h"
10 #include "chrome/browser/extensions/extension_service.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/ui/app_list/app_list_service.h"
13 #include "chrome/browser/ui/app_list/extension_app_item.h"
14 #include "chrome/browser/ui/app_list/extension_app_model_builder.h"
15 #include "chrome/browser/ui/host_desktop.h"
16 #include "chrome/common/chrome_switches.h"
17 #include "chrome/common/extensions/extension_constants.h"
18 #include "content/public/browser/notification_source.h"
19 #include "extensions/browser/extension_prefs.h"
20 #include "extensions/browser/extension_system.h"
21 #include "extensions/browser/uninstall_reason.h"
22 #include "grit/generated_resources.h"
23 #include "sync/api/sync_change_processor.h"
24 #include "sync/api/sync_data.h"
25 #include "sync/api/sync_merge_result.h"
26 #include "sync/protocol/sync.pb.h"
27 #include "ui/app_list/app_list_folder_item.h"
28 #include "ui/app_list/app_list_item.h"
29 #include "ui/app_list/app_list_model.h"
30 #include "ui/app_list/app_list_model_observer.h"
31 #include "ui/app_list/app_list_switches.h"
32 #include "ui/base/l10n/l10n_util.h"
34 #if defined(OS_CHROMEOS)
35 #include "chrome/browser/chromeos/file_manager/app_id.h"
36 #include "chrome/browser/chromeos/genius_app/app_id.h"
39 using syncer::SyncChange;
45 const char kOemFolderId[] = "ddb1da55-d478-4243-8642-56d3041f0263";
47 void UpdateSyncItemFromSync(const sync_pb::AppListSpecifics& specifics,
48 AppListSyncableService::SyncItem* item) {
49 DCHECK_EQ(item->item_id, specifics.item_id());
50 item->item_type = specifics.item_type();
51 item->item_name = specifics.item_name();
52 item->parent_id = specifics.parent_id();
53 if (!specifics.page_ordinal().empty())
54 item->page_ordinal = syncer::StringOrdinal(specifics.page_ordinal());
55 if (!specifics.item_ordinal().empty())
56 item->item_ordinal = syncer::StringOrdinal(specifics.item_ordinal());
59 bool UpdateSyncItemFromAppItem(const AppListItem* app_item,
60 AppListSyncableService::SyncItem* sync_item) {
61 DCHECK_EQ(sync_item->item_id, app_item->id());
63 if (app_list::switches::IsFolderUIEnabled() &&
64 sync_item->parent_id != app_item->folder_id()) {
65 sync_item->parent_id = app_item->folder_id();
68 if (sync_item->item_name != app_item->name()) {
69 sync_item->item_name = app_item->name();
72 if (!sync_item->item_ordinal.IsValid() ||
73 !app_item->position().Equals(sync_item->item_ordinal)) {
74 sync_item->item_ordinal = app_item->position();
77 // TODO(stevenjb): Set page_ordinal.
81 void GetSyncSpecificsFromSyncItem(const AppListSyncableService::SyncItem* item,
82 sync_pb::AppListSpecifics* specifics) {
84 specifics->set_item_id(item->item_id);
85 specifics->set_item_type(item->item_type);
86 specifics->set_item_name(item->item_name);
87 specifics->set_parent_id(item->parent_id);
88 if (item->page_ordinal.IsValid())
89 specifics->set_page_ordinal(item->page_ordinal.ToInternalValue());
90 if (item->item_ordinal.IsValid())
91 specifics->set_item_ordinal(item->item_ordinal.ToInternalValue());
94 syncer::SyncData GetSyncDataFromSyncItem(
95 const AppListSyncableService::SyncItem* item) {
96 sync_pb::EntitySpecifics specifics;
97 GetSyncSpecificsFromSyncItem(item, specifics.mutable_app_list());
98 return syncer::SyncData::CreateLocalData(item->item_id,
103 bool AppIsDefault(ExtensionService* service, const std::string& id) {
104 return service && extensions::ExtensionPrefs::Get(service->profile())
105 ->WasInstalledByDefault(id);
108 bool IsUnRemovableDefaultApp(const std::string& id) {
109 if (id == extension_misc::kChromeAppId ||
110 id == extension_misc::kWebStoreAppId)
112 #if defined(OS_CHROMEOS)
113 if (id == file_manager::kFileManagerAppId || id == genius_app::kGeniusAppId)
119 void UninstallExtension(ExtensionService* service, const std::string& id) {
120 if (service && service->GetInstalledExtension(id)) {
121 service->UninstallExtension(id,
122 extensions::UNINSTALL_REASON_SYNC,
123 base::Bind(&base::DoNothing),
128 bool GetAppListItemType(AppListItem* item,
129 sync_pb::AppListSpecifics::AppListItemType* type) {
130 const char* item_type = item->GetItemType();
131 if (item_type == ExtensionAppItem::kItemType) {
132 *type = sync_pb::AppListSpecifics::TYPE_APP;
133 } else if (item_type == AppListFolderItem::kItemType) {
134 *type = sync_pb::AppListSpecifics::TYPE_FOLDER;
136 LOG(ERROR) << "Unrecognized model type: " << item_type;
144 // AppListSyncableService::SyncItem
146 AppListSyncableService::SyncItem::SyncItem(
147 const std::string& id,
148 sync_pb::AppListSpecifics::AppListItemType type)
153 AppListSyncableService::SyncItem::~SyncItem() {
156 // AppListSyncableService::ModelObserver
158 class AppListSyncableService::ModelObserver : public AppListModelObserver {
160 explicit ModelObserver(AppListSyncableService* owner)
163 DVLOG(2) << owner_ << ": ModelObserver Added";
164 owner_->model()->AddObserver(this);
167 virtual ~ModelObserver() {
168 owner_->model()->RemoveObserver(this);
169 DVLOG(2) << owner_ << ": ModelObserver Removed";
173 // AppListModelObserver
174 virtual void OnAppListItemAdded(AppListItem* item) OVERRIDE {
175 DCHECK(!adding_item_);
176 adding_item_ = item; // Ignore updates while adding an item.
177 VLOG(2) << owner_ << " OnAppListItemAdded: " << item->ToDebugString();
178 owner_->AddOrUpdateFromSyncItem(item);
182 virtual void OnAppListItemWillBeDeleted(AppListItem* item) OVERRIDE {
183 DCHECK(!adding_item_);
184 VLOG(2) << owner_ << " OnAppListItemDeleted: " << item->ToDebugString();
185 // Don't sync folder removal in case the folder still exists on another
186 // device (e.g. with device specific items in it). Empty folders will be
187 // deleted when the last item is removed (in PruneEmptySyncFolders()).
188 if (item->GetItemType() == AppListFolderItem::kItemType)
190 owner_->RemoveSyncItem(item->id());
193 virtual void OnAppListItemUpdated(AppListItem* item) OVERRIDE {
195 // Adding an item may trigger update notifications which should be
197 DCHECK_EQ(adding_item_, item);
200 VLOG(2) << owner_ << " OnAppListItemUpdated: " << item->ToDebugString();
201 owner_->UpdateSyncItem(item);
204 AppListSyncableService* owner_;
205 AppListItem* adding_item_; // Unowned pointer to item being added.
207 DISALLOW_COPY_AND_ASSIGN(ModelObserver);
210 // AppListSyncableService
212 AppListSyncableService::AppListSyncableService(
214 extensions::ExtensionSystem* extension_system)
216 extension_system_(extension_system),
217 model_(new AppListModel),
218 initial_sync_data_processed_(false),
219 first_app_list_sync_(true) {
220 if (!extension_system) {
221 LOG(ERROR) << "AppListSyncableService created with no ExtensionSystem";
226 l10n_util::GetStringUTF8(IDS_APP_LIST_OEM_DEFAULT_FOLDER_NAME);
228 // Note: model_observer_ is constructed after the initial sync changes are
229 // received in MergeDataAndStartSyncing(). Changes to the model before that
230 // will be synced after the initial sync occurs.
231 if (extension_system->extension_service() &&
232 extension_system->extension_service()->is_ready()) {
237 // The extensions for this profile have not yet all been loaded.
239 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
240 content::Source<Profile>(profile));
243 AppListSyncableService::~AppListSyncableService() {
245 model_observer_.reset();
247 STLDeleteContainerPairSecondPointers(sync_items_.begin(), sync_items_.end());
250 void AppListSyncableService::BuildModel() {
251 // For now, use the AppListControllerDelegate associated with the native
252 // desktop. TODO(stevenjb): Remove ExtensionAppModelBuilder controller
253 // dependency and move the dependent methods from AppListControllerDelegate
254 // to an extension service delegate associated with this class.
255 AppListControllerDelegate* controller = NULL;
256 AppListService* service =
257 AppListService::Get(chrome::HOST_DESKTOP_TYPE_NATIVE);
259 controller = service->GetControllerDelegate();
260 apps_builder_.reset(new ExtensionAppModelBuilder(controller));
262 if (app_list::switches::IsAppListSyncEnabled()) {
263 VLOG(1) << this << ": AppListSyncableService: InitializeWithService.";
265 apps_builder_->InitializeWithService(this);
267 VLOG(1) << this << ": AppListSyncableService: InitializeWithProfile.";
268 apps_builder_->InitializeWithProfile(profile_, model_.get());
271 if (app_list::switches::IsDriveAppsInAppListEnabled())
272 drive_app_provider_.reset(new DriveAppProvider(profile_));
275 void AppListSyncableService::Shutdown() {
276 // DriveAppProvider touches other KeyedServices in its dtor and needs be
277 // released in shutdown stage.
278 drive_app_provider_.reset();
281 void AppListSyncableService::Observe(
283 const content::NotificationSource& source,
284 const content::NotificationDetails& details) {
285 DCHECK_EQ(extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED, type);
286 DCHECK_EQ(profile_, content::Source<Profile>(source).ptr());
287 registrar_.RemoveAll();
291 const AppListSyncableService::SyncItem*
292 AppListSyncableService::GetSyncItem(const std::string& id) const {
293 SyncItemMap::const_iterator iter = sync_items_.find(id);
294 if (iter != sync_items_.end())
299 void AppListSyncableService::SetOemFolderName(const std::string& name) {
300 oem_folder_name_ = name;
301 AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
303 model_->SetItemName(oem_folder, oem_folder_name_);
306 void AppListSyncableService::AddItem(scoped_ptr<AppListItem> app_item) {
307 SyncItem* sync_item = FindOrAddSyncItem(app_item.get());
309 return; // Item is not valid.
311 std::string folder_id;
312 if (app_list::switches::IsFolderUIEnabled()) {
313 if (AppIsOem(app_item->id())) {
314 folder_id = FindOrCreateOemFolder();
315 VLOG_IF(2, !folder_id.empty())
316 << this << ": AddItem to OEM folder: " << sync_item->ToString();
318 folder_id = sync_item->parent_id;
321 VLOG(2) << this << ": AddItem: " << sync_item->ToString()
322 << " Folder: '" << folder_id << "'";
323 model_->AddItemToFolder(app_item.Pass(), folder_id);
326 AppListSyncableService::SyncItem* AppListSyncableService::FindOrAddSyncItem(
327 AppListItem* app_item) {
328 const std::string& item_id = app_item->id();
329 if (item_id.empty()) {
330 LOG(ERROR) << "AppListItem item with empty ID";
333 SyncItem* sync_item = FindSyncItem(item_id);
335 // If there is an existing, non-REMOVE_DEFAULT entry, return it.
336 if (sync_item->item_type !=
337 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
338 DVLOG(2) << this << ": AddItem already exists: " << sync_item->ToString();
342 if (RemoveDefaultApp(app_item, sync_item))
345 // Fall through. The REMOVE_DEFAULT_APP entry has been deleted, now a new
346 // App entry can be added.
349 return CreateSyncItemFromAppItem(app_item);
352 AppListSyncableService::SyncItem*
353 AppListSyncableService::CreateSyncItemFromAppItem(AppListItem* app_item) {
354 sync_pb::AppListSpecifics::AppListItemType type;
355 if (!GetAppListItemType(app_item, &type))
357 VLOG(2) << this << " CreateSyncItemFromAppItem:" << app_item->ToDebugString();
358 SyncItem* sync_item = CreateSyncItem(app_item->id(), type);
359 UpdateSyncItemFromAppItem(app_item, sync_item);
360 SendSyncChange(sync_item, SyncChange::ACTION_ADD);
364 void AppListSyncableService::AddOrUpdateFromSyncItem(AppListItem* app_item) {
365 // Do not create a sync item for the OEM folder here, do that in
366 // ResolveFolderPositions once the position has been resolved.
367 if (app_item->id() == kOemFolderId)
370 SyncItem* sync_item = FindSyncItem(app_item->id());
372 UpdateAppItemFromSyncItem(sync_item, app_item);
375 CreateSyncItemFromAppItem(app_item);
378 bool AppListSyncableService::RemoveDefaultApp(AppListItem* item,
379 SyncItem* sync_item) {
380 CHECK_EQ(sync_item->item_type,
381 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP);
383 // If there is an existing REMOVE_DEFAULT_APP entry, and the app is
384 // installed as a Default app, uninstall the app instead of adding it.
385 if (sync_item->item_type == sync_pb::AppListSpecifics::TYPE_APP &&
386 AppIsDefault(extension_system_->extension_service(), item->id())) {
387 VLOG(2) << this << ": HandleDefaultApp: Uninstall: "
388 << sync_item->ToString();
389 UninstallExtension(extension_system_->extension_service(), item->id());
393 // Otherwise, we are adding the app as a non-default app (i.e. an app that
394 // was installed by Default and removed is getting installed explicitly by
395 // the user), so delete the REMOVE_DEFAULT_APP.
396 DeleteSyncItem(sync_item);
400 void AppListSyncableService::DeleteSyncItem(SyncItem* sync_item) {
402 VLOG(2) << this << " -> SYNC DELETE: " << sync_item->ToString();
403 SyncChange sync_change(FROM_HERE, SyncChange::ACTION_DELETE,
404 GetSyncDataFromSyncItem(sync_item));
405 sync_processor_->ProcessSyncChanges(
406 FROM_HERE, syncer::SyncChangeList(1, sync_change));
408 std::string item_id = sync_item->item_id;
410 sync_items_.erase(item_id);
413 void AppListSyncableService::UpdateSyncItem(AppListItem* app_item) {
414 SyncItem* sync_item = FindSyncItem(app_item->id());
416 LOG(ERROR) << "UpdateItem: no sync item: " << app_item->id();
419 bool changed = UpdateSyncItemFromAppItem(app_item, sync_item);
421 DVLOG(2) << this << " - Update: SYNC NO CHANGE: " << sync_item->ToString();
424 SendSyncChange(sync_item, SyncChange::ACTION_UPDATE);
427 void AppListSyncableService::RemoveItem(const std::string& id) {
429 model_->DeleteItem(id);
430 PruneEmptySyncFolders();
433 void AppListSyncableService::UpdateItem(AppListItem* app_item) {
434 // Check to see if the item needs to be moved to/from the OEM folder.
435 if (!app_list::switches::IsFolderUIEnabled())
437 bool is_oem = AppIsOem(app_item->id());
438 if (!is_oem && app_item->folder_id() == kOemFolderId)
439 model_->MoveItemToFolder(app_item, "");
440 else if (is_oem && app_item->folder_id() != kOemFolderId)
441 model_->MoveItemToFolder(app_item, kOemFolderId);
444 void AppListSyncableService::RemoveSyncItem(const std::string& id) {
445 VLOG(2) << this << ": RemoveSyncItem: " << id.substr(0, 8);
446 SyncItemMap::iterator iter = sync_items_.find(id);
447 if (iter == sync_items_.end()) {
448 DVLOG(2) << this << " : RemoveSyncItem: No Item.";
452 // Check for existing RemoveDefault sync item.
453 SyncItem* sync_item = iter->second;
454 sync_pb::AppListSpecifics::AppListItemType type = sync_item->item_type;
455 if (type == sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
456 // RemoveDefault item exists, just return.
457 DVLOG(2) << this << " : RemoveDefault Item exists.";
461 if (type == sync_pb::AppListSpecifics::TYPE_APP &&
462 AppIsDefault(extension_system_->extension_service(), id)) {
463 // This is a Default app; update the entry to a REMOVE_DEFAULT entry. This
464 // will overwrite any existing entry for the item.
465 VLOG(2) << this << " -> SYNC UPDATE: REMOVE_DEFAULT: "
466 << sync_item->item_id;
467 sync_item->item_type = sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP;
468 SendSyncChange(sync_item, SyncChange::ACTION_UPDATE);
472 DeleteSyncItem(sync_item);
475 void AppListSyncableService::ResolveFolderPositions() {
476 if (!app_list::switches::IsFolderUIEnabled())
479 VLOG(1) << "ResolveFolderPositions.";
480 for (SyncItemMap::iterator iter = sync_items_.begin();
481 iter != sync_items_.end(); ++iter) {
482 SyncItem* sync_item = iter->second;
483 if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
485 AppListItem* app_item = model_->FindItem(sync_item->item_id);
488 UpdateAppItemFromSyncItem(sync_item, app_item);
491 // Move the OEM folder if one exists and we have not synced its position.
492 AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
493 if (oem_folder && !FindSyncItem(kOemFolderId)) {
494 model_->SetItemPosition(oem_folder, GetOemFolderPos());
495 VLOG(1) << "Creating new OEM folder sync item: "
496 << oem_folder->position().ToDebugString();
497 CreateSyncItemFromAppItem(oem_folder);
501 void AppListSyncableService::PruneEmptySyncFolders() {
502 if (!app_list::switches::IsFolderUIEnabled())
505 std::set<std::string> parent_ids;
506 for (SyncItemMap::iterator iter = sync_items_.begin();
507 iter != sync_items_.end(); ++iter) {
508 parent_ids.insert(iter->second->parent_id);
510 for (SyncItemMap::iterator iter = sync_items_.begin();
511 iter != sync_items_.end(); ) {
512 SyncItem* sync_item = (iter++)->second;
513 if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
515 if (!ContainsKey(parent_ids, sync_item->item_id))
516 DeleteSyncItem(sync_item);
520 // AppListSyncableService syncer::SyncableService
522 syncer::SyncMergeResult AppListSyncableService::MergeDataAndStartSyncing(
523 syncer::ModelType type,
524 const syncer::SyncDataList& initial_sync_data,
525 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
526 scoped_ptr<syncer::SyncErrorFactory> error_handler) {
527 DCHECK(!sync_processor_.get());
528 DCHECK(sync_processor.get());
529 DCHECK(error_handler.get());
531 sync_processor_ = sync_processor.Pass();
532 sync_error_handler_ = error_handler.Pass();
533 if (switches::IsFolderUIEnabled())
534 model_->SetFoldersEnabled(true);
536 syncer::SyncMergeResult result = syncer::SyncMergeResult(type);
537 result.set_num_items_before_association(sync_items_.size());
538 VLOG(1) << this << ": MergeDataAndStartSyncing: "
539 << initial_sync_data.size();
541 // Copy all sync items to |unsynced_items|.
542 std::set<std::string> unsynced_items;
543 for (SyncItemMap::const_iterator iter = sync_items_.begin();
544 iter != sync_items_.end(); ++iter) {
545 unsynced_items.insert(iter->first);
548 // Create SyncItem entries for initial_sync_data.
549 size_t new_items = 0, updated_items = 0;
550 for (syncer::SyncDataList::const_iterator iter = initial_sync_data.begin();
551 iter != initial_sync_data.end(); ++iter) {
552 const syncer::SyncData& data = *iter;
553 const std::string& item_id = data.GetSpecifics().app_list().item_id();
554 const sync_pb::AppListSpecifics& specifics = data.GetSpecifics().app_list();
555 DVLOG(2) << this << " Initial Sync Item: " << item_id
556 << " Type: " << specifics.item_type();
557 DCHECK_EQ(syncer::APP_LIST, data.GetDataType());
558 if (ProcessSyncItemSpecifics(specifics))
562 if (specifics.item_type() != sync_pb::AppListSpecifics::TYPE_FOLDER &&
563 !IsUnRemovableDefaultApp(item_id) &&
564 !AppIsOem(item_id) &&
565 !AppIsDefault(extension_system_->extension_service(), item_id)) {
566 VLOG(2) << "Syncing non-default item: " << item_id;
567 first_app_list_sync_ = false;
569 unsynced_items.erase(item_id);
571 result.set_num_items_after_association(sync_items_.size());
572 result.set_num_items_added(new_items);
573 result.set_num_items_deleted(0);
574 result.set_num_items_modified(updated_items);
576 // Initial sync data has been processed, it is safe now to add new sync items.
577 initial_sync_data_processed_ = true;
579 // Send unsynced items. Does not affect |result|.
580 syncer::SyncChangeList change_list;
581 for (std::set<std::string>::iterator iter = unsynced_items.begin();
582 iter != unsynced_items.end(); ++iter) {
583 SyncItem* sync_item = FindSyncItem(*iter);
584 // Sync can cause an item to change folders, causing an unsynced folder
585 // item to be removed.
588 VLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
589 change_list.push_back(SyncChange(FROM_HERE, SyncChange::ACTION_ADD,
590 GetSyncDataFromSyncItem(sync_item)));
592 sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
594 // Adding items may have created folders without setting their positions
595 // since we haven't started observing the item list yet. Resolve those.
596 ResolveFolderPositions();
598 // Start observing app list model changes.
599 model_observer_.reset(new ModelObserver(this));
604 void AppListSyncableService::StopSyncing(syncer::ModelType type) {
605 DCHECK_EQ(type, syncer::APP_LIST);
607 sync_processor_.reset();
608 sync_error_handler_.reset();
609 model_->SetFoldersEnabled(false);
612 syncer::SyncDataList AppListSyncableService::GetAllSyncData(
613 syncer::ModelType type) const {
614 DCHECK_EQ(syncer::APP_LIST, type);
616 VLOG(1) << this << ": GetAllSyncData: " << sync_items_.size();
617 syncer::SyncDataList list;
618 for (SyncItemMap::const_iterator iter = sync_items_.begin();
619 iter != sync_items_.end(); ++iter) {
620 VLOG(2) << this << " -> SYNC: " << iter->second->ToString();
621 list.push_back(GetSyncDataFromSyncItem(iter->second));
626 syncer::SyncError AppListSyncableService::ProcessSyncChanges(
627 const tracked_objects::Location& from_here,
628 const syncer::SyncChangeList& change_list) {
629 if (!sync_processor_.get()) {
630 return syncer::SyncError(FROM_HERE,
631 syncer::SyncError::DATATYPE_ERROR,
632 "App List syncable service is not started.",
636 // Don't observe the model while processing incoming sync changes.
637 model_observer_.reset();
639 VLOG(1) << this << ": ProcessSyncChanges: " << change_list.size();
640 for (syncer::SyncChangeList::const_iterator iter = change_list.begin();
641 iter != change_list.end(); ++iter) {
642 const SyncChange& change = *iter;
643 VLOG(2) << this << " Change: "
644 << change.sync_data().GetSpecifics().app_list().item_id()
645 << " (" << change.change_type() << ")";
646 if (change.change_type() == SyncChange::ACTION_ADD ||
647 change.change_type() == SyncChange::ACTION_UPDATE) {
648 ProcessSyncItemSpecifics(change.sync_data().GetSpecifics().app_list());
649 } else if (change.change_type() == SyncChange::ACTION_DELETE) {
650 DeleteSyncItemSpecifics(change.sync_data().GetSpecifics().app_list());
652 LOG(ERROR) << "Invalid sync change";
656 // Continue observing app list model changes.
657 model_observer_.reset(new ModelObserver(this));
659 return syncer::SyncError();
662 // AppListSyncableService private
664 bool AppListSyncableService::ProcessSyncItemSpecifics(
665 const sync_pb::AppListSpecifics& specifics) {
666 const std::string& item_id = specifics.item_id();
667 if (item_id.empty()) {
668 LOG(ERROR) << "AppList item with empty ID";
671 SyncItem* sync_item = FindSyncItem(item_id);
673 // If an item of the same type exists, update it.
674 if (sync_item->item_type == specifics.item_type()) {
675 UpdateSyncItemFromSync(specifics, sync_item);
676 ProcessExistingSyncItem(sync_item);
677 VLOG(2) << this << " <- SYNC UPDATE: " << sync_item->ToString();
680 // Otherwise, one of the entries should be TYPE_REMOVE_DEFAULT_APP.
681 if (sync_item->item_type !=
682 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP &&
683 specifics.item_type() !=
684 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
685 LOG(ERROR) << "Synced item type: " << specifics.item_type()
686 << " != existing sync item type: " << sync_item->item_type
687 << " Deleting item from model!";
688 model_->DeleteItem(item_id);
690 VLOG(2) << this << " - ProcessSyncItem: Delete existing entry: "
691 << sync_item->ToString();
693 sync_items_.erase(item_id);
696 sync_item = CreateSyncItem(item_id, specifics.item_type());
697 UpdateSyncItemFromSync(specifics, sync_item);
698 ProcessNewSyncItem(sync_item);
699 VLOG(2) << this << " <- SYNC ADD: " << sync_item->ToString();
703 void AppListSyncableService::ProcessNewSyncItem(SyncItem* sync_item) {
704 VLOG(2) << "ProcessNewSyncItem: " << sync_item->ToString();
705 switch (sync_item->item_type) {
706 case sync_pb::AppListSpecifics::TYPE_APP: {
707 // New apps are added through ExtensionAppModelBuilder.
708 // TODO(stevenjb): Determine how to handle app items in sync that
709 // are not installed (e.g. default / OEM apps).
712 case sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP: {
713 VLOG(1) << this << ": Uninstall: " << sync_item->ToString();
714 UninstallExtension(extension_system_->extension_service(),
718 case sync_pb::AppListSpecifics::TYPE_FOLDER: {
719 AppListItem* app_item = model_->FindItem(sync_item->item_id);
721 return; // Don't create new folders here, the model will do that.
722 UpdateAppItemFromSyncItem(sync_item, app_item);
725 case sync_pb::AppListSpecifics::TYPE_URL: {
726 // TODO(stevenjb): Implement
727 LOG(WARNING) << "TYPE_URL not supported";
731 NOTREACHED() << "Unrecognized sync item type: " << sync_item->ToString();
734 void AppListSyncableService::ProcessExistingSyncItem(SyncItem* sync_item) {
735 if (sync_item->item_type ==
736 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
739 VLOG(2) << "ProcessExistingSyncItem: " << sync_item->ToString();
740 AppListItem* app_item = model_->FindItem(sync_item->item_id);
741 DVLOG(2) << " AppItem: " << app_item->ToDebugString();
743 LOG(ERROR) << "Item not found in model: " << sync_item->ToString();
746 // This is the only place where sync can cause an item to change folders.
747 if (app_list::switches::IsFolderUIEnabled() &&
748 app_item->folder_id() != sync_item->parent_id &&
749 !AppIsOem(app_item->id())) {
750 VLOG(2) << " Moving Item To Folder: " << sync_item->parent_id;
751 model_->MoveItemToFolder(app_item, sync_item->parent_id);
753 UpdateAppItemFromSyncItem(sync_item, app_item);
756 void AppListSyncableService::UpdateAppItemFromSyncItem(
757 const AppListSyncableService::SyncItem* sync_item,
758 AppListItem* app_item) {
759 VLOG(2) << this << " UpdateAppItemFromSyncItem: " << sync_item->ToString();
760 if (!app_item->position().Equals(sync_item->item_ordinal))
761 model_->SetItemPosition(app_item, sync_item->item_ordinal);
762 // Only update the item name if it is a Folder or the name is empty.
763 if (sync_item->item_name != app_item->name() &&
764 sync_item->item_id != kOemFolderId &&
765 (app_item->GetItemType() == AppListFolderItem::kItemType ||
766 app_item->name().empty())) {
767 model_->SetItemName(app_item, sync_item->item_name);
771 bool AppListSyncableService::SyncStarted() {
772 if (sync_processor_.get())
774 if (flare_.is_null()) {
775 VLOG(1) << this << ": SyncStarted: Flare.";
776 flare_ = sync_start_util::GetFlareForSyncableService(profile_->GetPath());
777 flare_.Run(syncer::APP_LIST);
782 void AppListSyncableService::SendSyncChange(
784 SyncChange::SyncChangeType sync_change_type) {
785 if (!SyncStarted()) {
786 DVLOG(2) << this << " - SendSyncChange: SYNC NOT STARTED: "
787 << sync_item->ToString();
790 if (!initial_sync_data_processed_ &&
791 sync_change_type == SyncChange::ACTION_ADD) {
792 // This can occur if an initial item is created before its folder item.
793 // A sync item should already exist for the folder, so we do not want to
794 // send an ADD event, since that would trigger a CHECK in the sync code.
795 DCHECK(sync_item->item_type == sync_pb::AppListSpecifics::TYPE_FOLDER);
796 DVLOG(2) << this << " - SendSyncChange: ADD before initial data processed: "
797 << sync_item->ToString();
800 if (sync_change_type == SyncChange::ACTION_ADD)
801 VLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
803 VLOG(2) << this << " -> SYNC UPDATE: " << sync_item->ToString();
804 SyncChange sync_change(FROM_HERE, sync_change_type,
805 GetSyncDataFromSyncItem(sync_item));
806 sync_processor_->ProcessSyncChanges(
807 FROM_HERE, syncer::SyncChangeList(1, sync_change));
810 AppListSyncableService::SyncItem*
811 AppListSyncableService::FindSyncItem(const std::string& item_id) {
812 SyncItemMap::iterator iter = sync_items_.find(item_id);
813 if (iter == sync_items_.end())
818 AppListSyncableService::SyncItem*
819 AppListSyncableService::CreateSyncItem(
820 const std::string& item_id,
821 sync_pb::AppListSpecifics::AppListItemType item_type) {
822 DCHECK(!ContainsKey(sync_items_, item_id));
823 SyncItem* sync_item = new SyncItem(item_id, item_type);
824 sync_items_[item_id] = sync_item;
828 void AppListSyncableService::DeleteSyncItemSpecifics(
829 const sync_pb::AppListSpecifics& specifics) {
830 const std::string& item_id = specifics.item_id();
831 if (item_id.empty()) {
832 LOG(ERROR) << "Delete AppList item with empty ID";
835 VLOG(2) << this << ": DeleteSyncItemSpecifics: " << item_id.substr(0, 8);
836 SyncItemMap::iterator iter = sync_items_.find(item_id);
837 if (iter == sync_items_.end())
839 sync_pb::AppListSpecifics::AppListItemType item_type =
840 iter->second->item_type;
841 VLOG(2) << this << " <- SYNC DELETE: " << iter->second->ToString();
843 sync_items_.erase(iter);
844 // Only delete apps from the model. Folders will be deleted when all
845 // children have been deleted.
846 if (item_type == sync_pb::AppListSpecifics::TYPE_APP)
847 model_->DeleteItem(item_id);
850 std::string AppListSyncableService::FindOrCreateOemFolder() {
851 AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
853 scoped_ptr<AppListFolderItem> new_folder(new AppListFolderItem(
854 kOemFolderId, AppListFolderItem::FOLDER_TYPE_OEM));
855 oem_folder = static_cast<AppListFolderItem*>(
856 model_->AddItem(new_folder.PassAs<app_list::AppListItem>()));
857 SyncItem* oem_sync_item = FindSyncItem(kOemFolderId);
859 VLOG(1) << "Creating OEM folder from existing sync item: "
860 << oem_sync_item->item_ordinal.ToDebugString();
861 model_->SetItemPosition(oem_folder, oem_sync_item->item_ordinal);
863 model_->SetItemPosition(oem_folder, GetOemFolderPos());
864 // Do not create a sync item for the OEM folder here, do it in
865 // ResolveFolderPositions() when the item position is finalized.
868 model_->SetItemName(oem_folder, oem_folder_name_);
869 return oem_folder->id();
872 syncer::StringOrdinal AppListSyncableService::GetOemFolderPos() {
873 VLOG(1) << "GetOemFolderPos: " << first_app_list_sync_;
874 if (!first_app_list_sync_) {
875 VLOG(1) << "Sync items exist, placing OEM folder at end.";
876 syncer::StringOrdinal last;
877 for (SyncItemMap::iterator iter = sync_items_.begin();
878 iter != sync_items_.end(); ++iter) {
879 SyncItem* sync_item = iter->second;
880 if (!last.IsValid() || sync_item->item_ordinal.GreaterThan(last))
881 last = sync_item->item_ordinal;
883 return last.CreateAfter();
886 // Place the OEM folder just after the web store, which should always be
887 // followed by a pre-installed app (e.g. Search), so the poosition should be
888 // stable. TODO(stevenjb): consider explicitly setting the OEM folder location
889 // along with the name in ServicesCustomizationDocument::SetOemFolderName().
890 AppListItemList* item_list = model_->top_level_item_list();
891 if (item_list->item_count() == 0)
892 return syncer::StringOrdinal();
894 size_t oem_index = 0;
895 for (; oem_index < item_list->item_count() - 1; ++oem_index) {
896 AppListItem* cur_item = item_list->item_at(oem_index);
897 if (cur_item->id() == extension_misc::kWebStoreAppId)
900 syncer::StringOrdinal oem_ordinal;
901 AppListItem* prev = item_list->item_at(oem_index);
902 if (oem_index + 1 < item_list->item_count()) {
903 AppListItem* next = item_list->item_at(oem_index + 1);
904 oem_ordinal = prev->position().CreateBetween(next->position());
906 oem_ordinal = prev->position().CreateAfter();
908 VLOG(1) << "Placing OEM Folder at: " << oem_index
909 << " position: " << oem_ordinal.ToDebugString();
913 bool AppListSyncableService::AppIsOem(const std::string& id) {
914 if (!extension_system_->extension_service())
916 const extensions::Extension* extension =
917 extension_system_->extension_service()->GetExtensionById(id, true);
918 return extension && extension->was_installed_by_oem();
921 std::string AppListSyncableService::SyncItem::ToString() const {
922 std::string res = item_id.substr(0, 8);
923 if (item_type == sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
924 res += " { RemoveDefault }";
926 res += " { " + item_name + " }";
927 res += " [" + item_ordinal.ToDebugString() + "]";
928 if (!parent_id.empty())
929 res += " <" + parent_id.substr(0, 8) + ">";
934 } // namespace app_list