Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / chrome / browser / ui / app_list / app_list_syncable_service.cc
blob7ab733bf9cf1cedee37148ac3bdb5fea5f055249
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 "base/strings/string_util.h"
9 #include "chrome/browser/apps/drive/drive_app_provider.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_prefs.h"
13 #include "chrome/browser/ui/app_list/app_list_service.h"
14 #include "chrome/browser/ui/app_list/extension_app_item.h"
15 #include "chrome/browser/ui/app_list/extension_app_model_builder.h"
16 #include "chrome/browser/ui/app_list/model_pref_updater.h"
17 #include "chrome/browser/ui/host_desktop.h"
18 #include "chrome/common/chrome_switches.h"
19 #include "chrome/common/extensions/extension_constants.h"
20 #include "chrome/grit/generated_resources.h"
21 #include "extensions/browser/extension_prefs.h"
22 #include "extensions/browser/extension_system.h"
23 #include "extensions/browser/uninstall_reason.h"
24 #include "extensions/common/constants.h"
25 #include "sync/api/sync_change_processor.h"
26 #include "sync/api/sync_data.h"
27 #include "sync/api/sync_merge_result.h"
28 #include "sync/protocol/sync.pb.h"
29 #include "ui/app_list/app_list_folder_item.h"
30 #include "ui/app_list/app_list_item.h"
31 #include "ui/app_list/app_list_model.h"
32 #include "ui/app_list/app_list_model_observer.h"
33 #include "ui/app_list/app_list_switches.h"
34 #include "ui/base/l10n/l10n_util.h"
36 #if defined(OS_CHROMEOS)
37 #include "chrome/browser/chromeos/file_manager/app_id.h"
38 #include "chrome/browser/chromeos/genius_app/app_id.h"
39 #endif
41 using syncer::SyncChange;
43 namespace app_list {
45 namespace {
47 const char kOemFolderId[] = "ddb1da55-d478-4243-8642-56d3041f0263";
49 // Prefix for a sync id of a Drive app. Drive app ids are in a different
50 // format and have to be used because a Drive app could have only an URL
51 // without a matching Chrome app. To differentiate the Drive app id from
52 // Chrome app ids, this prefix will be added to create the sync item id
53 // for a Drive app item.
54 const char kDriveAppSyncIdPrefix[] = "drive-app-";
56 void UpdateSyncItemFromSync(const sync_pb::AppListSpecifics& specifics,
57 AppListSyncableService::SyncItem* item) {
58 DCHECK_EQ(item->item_id, specifics.item_id());
59 item->item_type = specifics.item_type();
60 item->item_name = specifics.item_name();
61 item->parent_id = specifics.parent_id();
62 if (!specifics.item_ordinal().empty())
63 item->item_ordinal = syncer::StringOrdinal(specifics.item_ordinal());
66 bool UpdateSyncItemFromAppItem(const AppListItem* app_item,
67 AppListSyncableService::SyncItem* sync_item) {
68 DCHECK_EQ(sync_item->item_id, app_item->id());
69 bool changed = false;
70 if (app_list::switches::IsFolderUIEnabled() &&
71 sync_item->parent_id != app_item->folder_id()) {
72 sync_item->parent_id = app_item->folder_id();
73 changed = true;
75 if (sync_item->item_name != app_item->name()) {
76 sync_item->item_name = app_item->name();
77 changed = true;
79 if (!sync_item->item_ordinal.IsValid() ||
80 !app_item->position().Equals(sync_item->item_ordinal)) {
81 sync_item->item_ordinal = app_item->position();
82 changed = true;
84 return changed;
87 void GetSyncSpecificsFromSyncItem(const AppListSyncableService::SyncItem* item,
88 sync_pb::AppListSpecifics* specifics) {
89 DCHECK(specifics);
90 specifics->set_item_id(item->item_id);
91 specifics->set_item_type(item->item_type);
92 specifics->set_item_name(item->item_name);
93 specifics->set_parent_id(item->parent_id);
94 if (item->item_ordinal.IsValid())
95 specifics->set_item_ordinal(item->item_ordinal.ToInternalValue());
98 syncer::SyncData GetSyncDataFromSyncItem(
99 const AppListSyncableService::SyncItem* item) {
100 sync_pb::EntitySpecifics specifics;
101 GetSyncSpecificsFromSyncItem(item, specifics.mutable_app_list());
102 return syncer::SyncData::CreateLocalData(item->item_id,
103 item->item_id,
104 specifics);
107 bool AppIsDefault(ExtensionService* service, const std::string& id) {
108 return service && extensions::ExtensionPrefs::Get(service->profile())
109 ->WasInstalledByDefault(id);
112 bool IsUnRemovableDefaultApp(const std::string& id) {
113 if (id == extension_misc::kChromeAppId ||
114 id == extensions::kWebStoreAppId)
115 return true;
116 #if defined(OS_CHROMEOS)
117 if (id == file_manager::kFileManagerAppId || id == genius_app::kGeniusAppId)
118 return true;
119 #endif
120 return false;
123 void UninstallExtension(ExtensionService* service, const std::string& id) {
124 if (service) {
125 ExtensionService::UninstallExtensionHelper(
126 service, id, extensions::UNINSTALL_REASON_SYNC);
130 bool GetAppListItemType(AppListItem* item,
131 sync_pb::AppListSpecifics::AppListItemType* type) {
132 const char* item_type = item->GetItemType();
133 if (item_type == ExtensionAppItem::kItemType) {
134 *type = sync_pb::AppListSpecifics::TYPE_APP;
135 } else if (item_type == AppListFolderItem::kItemType) {
136 *type = sync_pb::AppListSpecifics::TYPE_FOLDER;
137 } else {
138 LOG(ERROR) << "Unrecognized model type: " << item_type;
139 return false;
141 return true;
144 bool IsDriveAppSyncId(const std::string& sync_id) {
145 return base::StartsWith(sync_id, kDriveAppSyncIdPrefix,
146 base::CompareCase::SENSITIVE);
149 std::string GetDriveAppSyncId(const std::string& drive_app_id) {
150 return kDriveAppSyncIdPrefix + drive_app_id;
153 std::string GetDriveAppIdFromSyncId(const std::string& sync_id) {
154 if (!IsDriveAppSyncId(sync_id))
155 return std::string();
156 return sync_id.substr(strlen(kDriveAppSyncIdPrefix));
159 } // namespace
161 // AppListSyncableService::SyncItem
163 AppListSyncableService::SyncItem::SyncItem(
164 const std::string& id,
165 sync_pb::AppListSpecifics::AppListItemType type)
166 : item_id(id),
167 item_type(type) {
170 AppListSyncableService::SyncItem::~SyncItem() {
173 // AppListSyncableService::ModelObserver
175 class AppListSyncableService::ModelObserver : public AppListModelObserver {
176 public:
177 explicit ModelObserver(AppListSyncableService* owner)
178 : owner_(owner),
179 adding_item_(NULL) {
180 DVLOG(2) << owner_ << ": ModelObserver Added";
181 owner_->GetModel()->AddObserver(this);
184 ~ModelObserver() override {
185 owner_->GetModel()->RemoveObserver(this);
186 DVLOG(2) << owner_ << ": ModelObserver Removed";
189 private:
190 // AppListModelObserver
191 void OnAppListItemAdded(AppListItem* item) override {
192 DCHECK(!adding_item_);
193 adding_item_ = item; // Ignore updates while adding an item.
194 VLOG(2) << owner_ << " OnAppListItemAdded: " << item->ToDebugString();
195 owner_->AddOrUpdateFromSyncItem(item);
196 adding_item_ = NULL;
199 void OnAppListItemWillBeDeleted(AppListItem* item) override {
200 DCHECK(!adding_item_);
201 VLOG(2) << owner_ << " OnAppListItemDeleted: " << item->ToDebugString();
202 // Don't sync folder removal in case the folder still exists on another
203 // device (e.g. with device specific items in it). Empty folders will be
204 // deleted when the last item is removed (in PruneEmptySyncFolders()).
205 if (item->GetItemType() == AppListFolderItem::kItemType)
206 return;
207 owner_->RemoveSyncItem(item->id());
210 void OnAppListItemUpdated(AppListItem* item) override {
211 if (adding_item_) {
212 // Adding an item may trigger update notifications which should be
213 // ignored.
214 DCHECK_EQ(adding_item_, item);
215 return;
217 VLOG(2) << owner_ << " OnAppListItemUpdated: " << item->ToDebugString();
218 owner_->UpdateSyncItem(item);
221 AppListSyncableService* owner_;
222 AppListItem* adding_item_; // Unowned pointer to item being added.
224 DISALLOW_COPY_AND_ASSIGN(ModelObserver);
227 // AppListSyncableService
229 AppListSyncableService::AppListSyncableService(
230 Profile* profile,
231 extensions::ExtensionSystem* extension_system)
232 : profile_(profile),
233 extension_system_(extension_system),
234 model_(new AppListModel),
235 initial_sync_data_processed_(false),
236 first_app_list_sync_(true) {
237 if (!extension_system) {
238 LOG(ERROR) << "AppListSyncableService created with no ExtensionSystem";
239 return;
242 oem_folder_name_ =
243 l10n_util::GetStringUTF8(IDS_APP_LIST_OEM_DEFAULT_FOLDER_NAME);
246 AppListSyncableService::~AppListSyncableService() {
247 // Remove observers.
248 model_observer_.reset();
249 model_pref_updater_.reset();
251 STLDeleteContainerPairSecondPointers(sync_items_.begin(), sync_items_.end());
254 void AppListSyncableService::BuildModel() {
255 // TODO(calamity): make this a DCHECK after a dev channel release.
256 CHECK(extension_system_->extension_service() &&
257 extension_system_->extension_service()->is_ready());
258 // For now, use the AppListControllerDelegate associated with the native
259 // desktop. TODO(stevenjb): Remove ExtensionAppModelBuilder controller
260 // dependency and move the dependent methods from AppListControllerDelegate
261 // to an extension service delegate associated with this class.
262 AppListControllerDelegate* controller = NULL;
263 AppListService* service =
264 AppListService::Get(chrome::HOST_DESKTOP_TYPE_NATIVE);
265 if (service)
266 controller = service->GetControllerDelegate();
267 apps_builder_.reset(new ExtensionAppModelBuilder(controller));
268 DCHECK(profile_);
269 if (app_list::switches::IsAppListSyncEnabled()) {
270 VLOG(1) << this << ": AppListSyncableService: InitializeWithService.";
271 SyncStarted();
272 apps_builder_->InitializeWithService(this, model_.get());
273 } else {
274 VLOG(1) << this << ": AppListSyncableService: InitializeWithProfile.";
275 apps_builder_->InitializeWithProfile(profile_, model_.get());
278 model_pref_updater_.reset(
279 new ModelPrefUpdater(AppListPrefs::Get(profile_), model_.get()));
281 if (app_list::switches::IsDriveAppsInAppListEnabled())
282 drive_app_provider_.reset(new DriveAppProvider(profile_, this));
285 size_t AppListSyncableService::GetNumSyncItemsForTest() {
286 // If the model isn't built yet, there will be no sync items.
287 GetModel();
289 return sync_items_.size();
292 void AppListSyncableService::ResetDriveAppProviderForTest() {
293 drive_app_provider_.reset();
296 void AppListSyncableService::Shutdown() {
297 // DriveAppProvider touches other KeyedServices in its dtor and needs be
298 // released in shutdown stage.
299 drive_app_provider_.reset();
302 void AppListSyncableService::TrackUninstalledDriveApp(
303 const std::string& drive_app_id) {
304 const std::string sync_id = GetDriveAppSyncId(drive_app_id);
305 SyncItem* sync_item = FindSyncItem(sync_id);
306 if (sync_item) {
307 DCHECK_EQ(sync_item->item_type,
308 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP);
309 return;
312 sync_item = CreateSyncItem(
313 sync_id, sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP);
314 SendSyncChange(sync_item, SyncChange::ACTION_ADD);
317 void AppListSyncableService::UntrackUninstalledDriveApp(
318 const std::string& drive_app_id) {
319 const std::string sync_id = GetDriveAppSyncId(drive_app_id);
320 SyncItem* sync_item = FindSyncItem(sync_id);
321 if (!sync_item)
322 return;
324 DCHECK_EQ(sync_item->item_type,
325 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP);
326 DeleteSyncItem(sync_item);
329 const AppListSyncableService::SyncItem*
330 AppListSyncableService::GetSyncItem(const std::string& id) const {
331 SyncItemMap::const_iterator iter = sync_items_.find(id);
332 if (iter != sync_items_.end())
333 return iter->second;
334 return NULL;
337 void AppListSyncableService::SetOemFolderName(const std::string& name) {
338 oem_folder_name_ = name;
339 AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
340 if (oem_folder)
341 model_->SetItemName(oem_folder, oem_folder_name_);
344 AppListModel* AppListSyncableService::GetModel() {
345 if (!apps_builder_)
346 BuildModel();
348 return model_.get();
351 void AppListSyncableService::AddItem(scoped_ptr<AppListItem> app_item) {
352 SyncItem* sync_item = FindOrAddSyncItem(app_item.get());
353 if (!sync_item)
354 return; // Item is not valid.
356 std::string folder_id;
357 if (app_list::switches::IsFolderUIEnabled()) {
358 if (AppIsOem(app_item->id())) {
359 folder_id = FindOrCreateOemFolder();
360 VLOG_IF(2, !folder_id.empty())
361 << this << ": AddItem to OEM folder: " << sync_item->ToString();
362 } else {
363 folder_id = sync_item->parent_id;
366 VLOG(2) << this << ": AddItem: " << sync_item->ToString()
367 << " Folder: '" << folder_id << "'";
368 model_->AddItemToFolder(app_item.Pass(), folder_id);
371 AppListSyncableService::SyncItem* AppListSyncableService::FindOrAddSyncItem(
372 AppListItem* app_item) {
373 const std::string& item_id = app_item->id();
374 if (item_id.empty()) {
375 LOG(ERROR) << "AppListItem item with empty ID";
376 return NULL;
378 SyncItem* sync_item = FindSyncItem(item_id);
379 if (sync_item) {
380 // If there is an existing, non-REMOVE_DEFAULT entry, return it.
381 if (sync_item->item_type !=
382 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
383 DVLOG(2) << this << ": AddItem already exists: " << sync_item->ToString();
384 return sync_item;
387 if (RemoveDefaultApp(app_item, sync_item))
388 return NULL;
390 // Fall through. The REMOVE_DEFAULT_APP entry has been deleted, now a new
391 // App entry can be added.
394 return CreateSyncItemFromAppItem(app_item);
397 AppListSyncableService::SyncItem*
398 AppListSyncableService::CreateSyncItemFromAppItem(AppListItem* app_item) {
399 sync_pb::AppListSpecifics::AppListItemType type;
400 if (!GetAppListItemType(app_item, &type))
401 return NULL;
402 VLOG(2) << this << " CreateSyncItemFromAppItem:" << app_item->ToDebugString();
403 SyncItem* sync_item = CreateSyncItem(app_item->id(), type);
404 UpdateSyncItemFromAppItem(app_item, sync_item);
405 SendSyncChange(sync_item, SyncChange::ACTION_ADD);
406 return sync_item;
409 void AppListSyncableService::AddOrUpdateFromSyncItem(AppListItem* app_item) {
410 // Do not create a sync item for the OEM folder here, do that in
411 // ResolveFolderPositions once the position has been resolved.
412 if (app_item->id() == kOemFolderId)
413 return;
415 SyncItem* sync_item = FindSyncItem(app_item->id());
416 if (sync_item) {
417 UpdateAppItemFromSyncItem(sync_item, app_item);
418 return;
420 CreateSyncItemFromAppItem(app_item);
423 bool AppListSyncableService::RemoveDefaultApp(AppListItem* item,
424 SyncItem* sync_item) {
425 CHECK_EQ(sync_item->item_type,
426 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP);
428 // If there is an existing REMOVE_DEFAULT_APP entry, and the app is
429 // installed as a Default app, uninstall the app instead of adding it.
430 if (sync_item->item_type == sync_pb::AppListSpecifics::TYPE_APP &&
431 AppIsDefault(extension_system_->extension_service(), item->id())) {
432 VLOG(2) << this << ": HandleDefaultApp: Uninstall: "
433 << sync_item->ToString();
434 UninstallExtension(extension_system_->extension_service(), item->id());
435 return true;
438 // Otherwise, we are adding the app as a non-default app (i.e. an app that
439 // was installed by Default and removed is getting installed explicitly by
440 // the user), so delete the REMOVE_DEFAULT_APP.
441 DeleteSyncItem(sync_item);
442 return false;
445 void AppListSyncableService::DeleteSyncItem(SyncItem* sync_item) {
446 if (SyncStarted()) {
447 VLOG(2) << this << " -> SYNC DELETE: " << sync_item->ToString();
448 SyncChange sync_change(FROM_HERE, SyncChange::ACTION_DELETE,
449 GetSyncDataFromSyncItem(sync_item));
450 sync_processor_->ProcessSyncChanges(
451 FROM_HERE, syncer::SyncChangeList(1, sync_change));
453 std::string item_id = sync_item->item_id;
454 delete sync_item;
455 sync_items_.erase(item_id);
458 void AppListSyncableService::UpdateSyncItem(AppListItem* app_item) {
459 SyncItem* sync_item = FindSyncItem(app_item->id());
460 if (!sync_item) {
461 LOG(ERROR) << "UpdateItem: no sync item: " << app_item->id();
462 return;
464 bool changed = UpdateSyncItemFromAppItem(app_item, sync_item);
465 if (!changed) {
466 DVLOG(2) << this << " - Update: SYNC NO CHANGE: " << sync_item->ToString();
467 return;
469 SendSyncChange(sync_item, SyncChange::ACTION_UPDATE);
472 void AppListSyncableService::RemoveItem(const std::string& id) {
473 RemoveSyncItem(id);
474 model_->DeleteItem(id);
475 PruneEmptySyncFolders();
478 void AppListSyncableService::RemoveUninstalledItem(const std::string& id) {
479 RemoveSyncItem(id);
480 model_->DeleteUninstalledItem(id);
481 PruneEmptySyncFolders();
484 void AppListSyncableService::UpdateItem(AppListItem* app_item) {
485 // Check to see if the item needs to be moved to/from the OEM folder.
486 if (!app_list::switches::IsFolderUIEnabled())
487 return;
488 bool is_oem = AppIsOem(app_item->id());
489 if (!is_oem && app_item->folder_id() == kOemFolderId)
490 model_->MoveItemToFolder(app_item, "");
491 else if (is_oem && app_item->folder_id() != kOemFolderId)
492 model_->MoveItemToFolder(app_item, kOemFolderId);
495 void AppListSyncableService::RemoveSyncItem(const std::string& id) {
496 VLOG(2) << this << ": RemoveSyncItem: " << id.substr(0, 8);
497 SyncItemMap::iterator iter = sync_items_.find(id);
498 if (iter == sync_items_.end()) {
499 DVLOG(2) << this << " : RemoveSyncItem: No Item.";
500 return;
503 // Check for existing RemoveDefault sync item.
504 SyncItem* sync_item = iter->second;
505 sync_pb::AppListSpecifics::AppListItemType type = sync_item->item_type;
506 if (type == sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
507 // RemoveDefault item exists, just return.
508 DVLOG(2) << this << " : RemoveDefault Item exists.";
509 return;
512 if (type == sync_pb::AppListSpecifics::TYPE_APP &&
513 AppIsDefault(extension_system_->extension_service(), id)) {
514 // This is a Default app; update the entry to a REMOVE_DEFAULT entry. This
515 // will overwrite any existing entry for the item.
516 VLOG(2) << this << " -> SYNC UPDATE: REMOVE_DEFAULT: "
517 << sync_item->item_id;
518 sync_item->item_type = sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP;
519 SendSyncChange(sync_item, SyncChange::ACTION_UPDATE);
520 return;
523 DeleteSyncItem(sync_item);
526 void AppListSyncableService::ResolveFolderPositions() {
527 if (!app_list::switches::IsFolderUIEnabled())
528 return;
530 VLOG(1) << "ResolveFolderPositions.";
531 for (SyncItemMap::iterator iter = sync_items_.begin();
532 iter != sync_items_.end(); ++iter) {
533 SyncItem* sync_item = iter->second;
534 if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
535 continue;
536 AppListItem* app_item = model_->FindItem(sync_item->item_id);
537 if (!app_item)
538 continue;
539 UpdateAppItemFromSyncItem(sync_item, app_item);
542 // Move the OEM folder if one exists and we have not synced its position.
543 AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
544 if (oem_folder && !FindSyncItem(kOemFolderId)) {
545 model_->SetItemPosition(oem_folder, GetOemFolderPos());
546 VLOG(1) << "Creating new OEM folder sync item: "
547 << oem_folder->position().ToDebugString();
548 CreateSyncItemFromAppItem(oem_folder);
552 void AppListSyncableService::PruneEmptySyncFolders() {
553 if (!app_list::switches::IsFolderUIEnabled())
554 return;
556 std::set<std::string> parent_ids;
557 for (SyncItemMap::iterator iter = sync_items_.begin();
558 iter != sync_items_.end(); ++iter) {
559 parent_ids.insert(iter->second->parent_id);
561 for (SyncItemMap::iterator iter = sync_items_.begin();
562 iter != sync_items_.end(); ) {
563 SyncItem* sync_item = (iter++)->second;
564 if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
565 continue;
566 if (!ContainsKey(parent_ids, sync_item->item_id))
567 DeleteSyncItem(sync_item);
571 // AppListSyncableService syncer::SyncableService
573 syncer::SyncMergeResult AppListSyncableService::MergeDataAndStartSyncing(
574 syncer::ModelType type,
575 const syncer::SyncDataList& initial_sync_data,
576 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
577 scoped_ptr<syncer::SyncErrorFactory> error_handler) {
578 DCHECK(!sync_processor_.get());
579 DCHECK(sync_processor.get());
580 DCHECK(error_handler.get());
582 // Ensure the model is built.
583 GetModel();
585 sync_processor_ = sync_processor.Pass();
586 sync_error_handler_ = error_handler.Pass();
587 if (switches::IsFolderUIEnabled())
588 model_->SetFoldersEnabled(true);
590 syncer::SyncMergeResult result = syncer::SyncMergeResult(type);
591 result.set_num_items_before_association(sync_items_.size());
592 VLOG(1) << this << ": MergeDataAndStartSyncing: "
593 << initial_sync_data.size();
595 // Copy all sync items to |unsynced_items|.
596 std::set<std::string> unsynced_items;
597 for (SyncItemMap::const_iterator iter = sync_items_.begin();
598 iter != sync_items_.end(); ++iter) {
599 unsynced_items.insert(iter->first);
602 // Create SyncItem entries for initial_sync_data.
603 size_t new_items = 0, updated_items = 0;
604 for (syncer::SyncDataList::const_iterator iter = initial_sync_data.begin();
605 iter != initial_sync_data.end(); ++iter) {
606 const syncer::SyncData& data = *iter;
607 const std::string& item_id = data.GetSpecifics().app_list().item_id();
608 const sync_pb::AppListSpecifics& specifics = data.GetSpecifics().app_list();
609 DVLOG(2) << this << " Initial Sync Item: " << item_id
610 << " Type: " << specifics.item_type();
611 DCHECK_EQ(syncer::APP_LIST, data.GetDataType());
612 if (ProcessSyncItemSpecifics(specifics))
613 ++new_items;
614 else
615 ++updated_items;
616 if (specifics.item_type() != sync_pb::AppListSpecifics::TYPE_FOLDER &&
617 !IsUnRemovableDefaultApp(item_id) &&
618 !AppIsOem(item_id) &&
619 !AppIsDefault(extension_system_->extension_service(), item_id)) {
620 VLOG(2) << "Syncing non-default item: " << item_id;
621 first_app_list_sync_ = false;
623 unsynced_items.erase(item_id);
625 result.set_num_items_after_association(sync_items_.size());
626 result.set_num_items_added(new_items);
627 result.set_num_items_deleted(0);
628 result.set_num_items_modified(updated_items);
630 // Initial sync data has been processed, it is safe now to add new sync items.
631 initial_sync_data_processed_ = true;
633 // Send unsynced items. Does not affect |result|.
634 syncer::SyncChangeList change_list;
635 for (std::set<std::string>::iterator iter = unsynced_items.begin();
636 iter != unsynced_items.end(); ++iter) {
637 SyncItem* sync_item = FindSyncItem(*iter);
638 // Sync can cause an item to change folders, causing an unsynced folder
639 // item to be removed.
640 if (!sync_item)
641 continue;
642 VLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
643 change_list.push_back(SyncChange(FROM_HERE, SyncChange::ACTION_ADD,
644 GetSyncDataFromSyncItem(sync_item)));
646 sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
648 // Adding items may have created folders without setting their positions
649 // since we haven't started observing the item list yet. Resolve those.
650 ResolveFolderPositions();
652 // Start observing app list model changes.
653 model_observer_.reset(new ModelObserver(this));
655 return result;
658 void AppListSyncableService::StopSyncing(syncer::ModelType type) {
659 DCHECK_EQ(type, syncer::APP_LIST);
661 sync_processor_.reset();
662 sync_error_handler_.reset();
663 model_->SetFoldersEnabled(false);
666 syncer::SyncDataList AppListSyncableService::GetAllSyncData(
667 syncer::ModelType type) const {
668 DCHECK_EQ(syncer::APP_LIST, type);
670 VLOG(1) << this << ": GetAllSyncData: " << sync_items_.size();
671 syncer::SyncDataList list;
672 for (SyncItemMap::const_iterator iter = sync_items_.begin();
673 iter != sync_items_.end(); ++iter) {
674 VLOG(2) << this << " -> SYNC: " << iter->second->ToString();
675 list.push_back(GetSyncDataFromSyncItem(iter->second));
677 return list;
680 syncer::SyncError AppListSyncableService::ProcessSyncChanges(
681 const tracked_objects::Location& from_here,
682 const syncer::SyncChangeList& change_list) {
683 if (!sync_processor_.get()) {
684 return syncer::SyncError(FROM_HERE,
685 syncer::SyncError::DATATYPE_ERROR,
686 "App List syncable service is not started.",
687 syncer::APP_LIST);
690 // Don't observe the model while processing incoming sync changes.
691 model_observer_.reset();
693 VLOG(1) << this << ": ProcessSyncChanges: " << change_list.size();
694 for (syncer::SyncChangeList::const_iterator iter = change_list.begin();
695 iter != change_list.end(); ++iter) {
696 const SyncChange& change = *iter;
697 VLOG(2) << this << " Change: "
698 << change.sync_data().GetSpecifics().app_list().item_id()
699 << " (" << change.change_type() << ")";
700 if (change.change_type() == SyncChange::ACTION_ADD ||
701 change.change_type() == SyncChange::ACTION_UPDATE) {
702 ProcessSyncItemSpecifics(change.sync_data().GetSpecifics().app_list());
703 } else if (change.change_type() == SyncChange::ACTION_DELETE) {
704 DeleteSyncItemSpecifics(change.sync_data().GetSpecifics().app_list());
705 } else {
706 LOG(ERROR) << "Invalid sync change";
710 // Continue observing app list model changes.
711 model_observer_.reset(new ModelObserver(this));
713 return syncer::SyncError();
716 // AppListSyncableService private
718 bool AppListSyncableService::ProcessSyncItemSpecifics(
719 const sync_pb::AppListSpecifics& specifics) {
720 const std::string& item_id = specifics.item_id();
721 if (item_id.empty()) {
722 LOG(ERROR) << "AppList item with empty ID";
723 return false;
725 SyncItem* sync_item = FindSyncItem(item_id);
726 if (sync_item) {
727 // If an item of the same type exists, update it.
728 if (sync_item->item_type == specifics.item_type()) {
729 UpdateSyncItemFromSync(specifics, sync_item);
730 ProcessExistingSyncItem(sync_item);
731 VLOG(2) << this << " <- SYNC UPDATE: " << sync_item->ToString();
732 return false;
734 // Otherwise, one of the entries should be TYPE_REMOVE_DEFAULT_APP.
735 if (sync_item->item_type !=
736 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP &&
737 specifics.item_type() !=
738 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
739 LOG(ERROR) << "Synced item type: " << specifics.item_type()
740 << " != existing sync item type: " << sync_item->item_type
741 << " Deleting item from model!";
742 model_->DeleteItem(item_id);
744 VLOG(2) << this << " - ProcessSyncItem: Delete existing entry: "
745 << sync_item->ToString();
746 delete sync_item;
747 sync_items_.erase(item_id);
750 sync_item = CreateSyncItem(item_id, specifics.item_type());
751 UpdateSyncItemFromSync(specifics, sync_item);
752 ProcessNewSyncItem(sync_item);
753 VLOG(2) << this << " <- SYNC ADD: " << sync_item->ToString();
754 return true;
757 void AppListSyncableService::ProcessNewSyncItem(SyncItem* sync_item) {
758 VLOG(2) << "ProcessNewSyncItem: " << sync_item->ToString();
759 switch (sync_item->item_type) {
760 case sync_pb::AppListSpecifics::TYPE_APP: {
761 // New apps are added through ExtensionAppModelBuilder.
762 // TODO(stevenjb): Determine how to handle app items in sync that
763 // are not installed (e.g. default / OEM apps).
764 return;
766 case sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP: {
767 VLOG(1) << this << ": Uninstall: " << sync_item->ToString();
768 if (IsDriveAppSyncId(sync_item->item_id)) {
769 if (drive_app_provider_) {
770 drive_app_provider_->AddUninstalledDriveAppFromSync(
771 GetDriveAppIdFromSyncId(sync_item->item_id));
773 } else {
774 UninstallExtension(extension_system_->extension_service(),
775 sync_item->item_id);
777 return;
779 case sync_pb::AppListSpecifics::TYPE_FOLDER: {
780 AppListItem* app_item = model_->FindItem(sync_item->item_id);
781 if (!app_item)
782 return; // Don't create new folders here, the model will do that.
783 UpdateAppItemFromSyncItem(sync_item, app_item);
784 return;
786 case sync_pb::AppListSpecifics::TYPE_URL: {
787 // TODO(stevenjb): Implement
788 LOG(WARNING) << "TYPE_URL not supported";
789 return;
792 NOTREACHED() << "Unrecognized sync item type: " << sync_item->ToString();
795 void AppListSyncableService::ProcessExistingSyncItem(SyncItem* sync_item) {
796 if (sync_item->item_type ==
797 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
798 return;
800 VLOG(2) << "ProcessExistingSyncItem: " << sync_item->ToString();
801 AppListItem* app_item = model_->FindItem(sync_item->item_id);
802 DVLOG(2) << " AppItem: " << app_item->ToDebugString();
803 if (!app_item) {
804 LOG(ERROR) << "Item not found in model: " << sync_item->ToString();
805 return;
807 // This is the only place where sync can cause an item to change folders.
808 if (app_list::switches::IsFolderUIEnabled() &&
809 app_item->folder_id() != sync_item->parent_id &&
810 !AppIsOem(app_item->id())) {
811 VLOG(2) << " Moving Item To Folder: " << sync_item->parent_id;
812 model_->MoveItemToFolder(app_item, sync_item->parent_id);
814 UpdateAppItemFromSyncItem(sync_item, app_item);
817 void AppListSyncableService::UpdateAppItemFromSyncItem(
818 const AppListSyncableService::SyncItem* sync_item,
819 AppListItem* app_item) {
820 VLOG(2) << this << " UpdateAppItemFromSyncItem: " << sync_item->ToString();
821 if (!app_item->position().Equals(sync_item->item_ordinal))
822 model_->SetItemPosition(app_item, sync_item->item_ordinal);
823 // Only update the item name if it is a Folder or the name is empty.
824 if (sync_item->item_name != app_item->name() &&
825 sync_item->item_id != kOemFolderId &&
826 (app_item->GetItemType() == AppListFolderItem::kItemType ||
827 app_item->name().empty())) {
828 model_->SetItemName(app_item, sync_item->item_name);
832 bool AppListSyncableService::SyncStarted() {
833 if (sync_processor_.get())
834 return true;
835 if (flare_.is_null()) {
836 VLOG(1) << this << ": SyncStarted: Flare.";
837 flare_ = sync_start_util::GetFlareForSyncableService(profile_->GetPath());
838 flare_.Run(syncer::APP_LIST);
840 return false;
843 void AppListSyncableService::SendSyncChange(
844 SyncItem* sync_item,
845 SyncChange::SyncChangeType sync_change_type) {
846 if (!SyncStarted()) {
847 DVLOG(2) << this << " - SendSyncChange: SYNC NOT STARTED: "
848 << sync_item->ToString();
849 return;
851 if (!initial_sync_data_processed_ &&
852 sync_change_type == SyncChange::ACTION_ADD) {
853 // This can occur if an initial item is created before its folder item.
854 // A sync item should already exist for the folder, so we do not want to
855 // send an ADD event, since that would trigger a CHECK in the sync code.
856 DCHECK(sync_item->item_type == sync_pb::AppListSpecifics::TYPE_FOLDER);
857 DVLOG(2) << this << " - SendSyncChange: ADD before initial data processed: "
858 << sync_item->ToString();
859 return;
861 if (sync_change_type == SyncChange::ACTION_ADD)
862 VLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
863 else
864 VLOG(2) << this << " -> SYNC UPDATE: " << sync_item->ToString();
865 SyncChange sync_change(FROM_HERE, sync_change_type,
866 GetSyncDataFromSyncItem(sync_item));
867 sync_processor_->ProcessSyncChanges(
868 FROM_HERE, syncer::SyncChangeList(1, sync_change));
871 AppListSyncableService::SyncItem*
872 AppListSyncableService::FindSyncItem(const std::string& item_id) {
873 SyncItemMap::iterator iter = sync_items_.find(item_id);
874 if (iter == sync_items_.end())
875 return NULL;
876 return iter->second;
879 AppListSyncableService::SyncItem*
880 AppListSyncableService::CreateSyncItem(
881 const std::string& item_id,
882 sync_pb::AppListSpecifics::AppListItemType item_type) {
883 DCHECK(!ContainsKey(sync_items_, item_id));
884 SyncItem* sync_item = new SyncItem(item_id, item_type);
885 sync_items_[item_id] = sync_item;
886 return sync_item;
889 void AppListSyncableService::DeleteSyncItemSpecifics(
890 const sync_pb::AppListSpecifics& specifics) {
891 const std::string& item_id = specifics.item_id();
892 if (item_id.empty()) {
893 LOG(ERROR) << "Delete AppList item with empty ID";
894 return;
896 VLOG(2) << this << ": DeleteSyncItemSpecifics: " << item_id.substr(0, 8);
897 SyncItemMap::iterator iter = sync_items_.find(item_id);
898 if (iter == sync_items_.end())
899 return;
900 sync_pb::AppListSpecifics::AppListItemType item_type =
901 iter->second->item_type;
902 VLOG(2) << this << " <- SYNC DELETE: " << iter->second->ToString();
903 delete iter->second;
904 sync_items_.erase(iter);
905 // Only delete apps from the model. Folders will be deleted when all
906 // children have been deleted.
907 if (item_type == sync_pb::AppListSpecifics::TYPE_APP) {
908 model_->DeleteItem(item_id);
909 } else if (item_type == sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
910 if (IsDriveAppSyncId(item_id) && drive_app_provider_) {
911 drive_app_provider_->RemoveUninstalledDriveAppFromSync(
912 GetDriveAppIdFromSyncId(item_id));
917 std::string AppListSyncableService::FindOrCreateOemFolder() {
918 AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
919 if (!oem_folder) {
920 scoped_ptr<AppListFolderItem> new_folder(new AppListFolderItem(
921 kOemFolderId, AppListFolderItem::FOLDER_TYPE_OEM));
922 oem_folder =
923 static_cast<AppListFolderItem*>(model_->AddItem(new_folder.Pass()));
924 SyncItem* oem_sync_item = FindSyncItem(kOemFolderId);
925 if (oem_sync_item) {
926 VLOG(1) << "Creating OEM folder from existing sync item: "
927 << oem_sync_item->item_ordinal.ToDebugString();
928 model_->SetItemPosition(oem_folder, oem_sync_item->item_ordinal);
929 } else {
930 model_->SetItemPosition(oem_folder, GetOemFolderPos());
931 // Do not create a sync item for the OEM folder here, do it in
932 // ResolveFolderPositions() when the item position is finalized.
935 model_->SetItemName(oem_folder, oem_folder_name_);
936 return oem_folder->id();
939 syncer::StringOrdinal AppListSyncableService::GetOemFolderPos() {
940 VLOG(1) << "GetOemFolderPos: " << first_app_list_sync_;
941 if (!first_app_list_sync_) {
942 VLOG(1) << "Sync items exist, placing OEM folder at end.";
943 syncer::StringOrdinal last;
944 for (SyncItemMap::iterator iter = sync_items_.begin();
945 iter != sync_items_.end(); ++iter) {
946 SyncItem* sync_item = iter->second;
947 if (sync_item->item_ordinal.IsValid() &&
948 (!last.IsValid() || sync_item->item_ordinal.GreaterThan(last))) {
949 last = sync_item->item_ordinal;
952 if (last.IsValid())
953 return last.CreateAfter();
956 // Place the OEM folder just after the web store, which should always be
957 // followed by a pre-installed app (e.g. Search), so the poosition should be
958 // stable. TODO(stevenjb): consider explicitly setting the OEM folder location
959 // along with the name in ServicesCustomizationDocument::SetOemFolderName().
960 AppListItemList* item_list = model_->top_level_item_list();
961 if (item_list->item_count() == 0)
962 return syncer::StringOrdinal();
964 size_t oem_index = 0;
965 for (; oem_index < item_list->item_count() - 1; ++oem_index) {
966 AppListItem* cur_item = item_list->item_at(oem_index);
967 if (cur_item->id() == extensions::kWebStoreAppId)
968 break;
970 syncer::StringOrdinal oem_ordinal;
971 AppListItem* prev = item_list->item_at(oem_index);
972 if (oem_index + 1 < item_list->item_count()) {
973 AppListItem* next = item_list->item_at(oem_index + 1);
974 oem_ordinal = prev->position().CreateBetween(next->position());
975 } else {
976 oem_ordinal = prev->position().CreateAfter();
978 VLOG(1) << "Placing OEM Folder at: " << oem_index
979 << " position: " << oem_ordinal.ToDebugString();
980 return oem_ordinal;
983 bool AppListSyncableService::AppIsOem(const std::string& id) {
984 if (!extension_system_->extension_service())
985 return false;
986 const extensions::Extension* extension =
987 extension_system_->extension_service()->GetExtensionById(id, true);
988 return extension && extension->was_installed_by_oem();
991 std::string AppListSyncableService::SyncItem::ToString() const {
992 std::string res = item_id.substr(0, 8);
993 if (item_type == sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
994 res += " { RemoveDefault }";
995 } else {
996 res += " { " + item_name + " }";
997 res += " [" + item_ordinal.ToDebugString() + "]";
998 if (!parent_id.empty())
999 res += " <" + parent_id.substr(0, 8) + ">";
1001 return res;
1004 } // namespace app_list