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"
41 using syncer::SyncChange
;
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());
70 if (app_list::switches::IsFolderUIEnabled() &&
71 sync_item
->parent_id
!= app_item
->folder_id()) {
72 sync_item
->parent_id
= app_item
->folder_id();
75 if (sync_item
->item_name
!= app_item
->name()) {
76 sync_item
->item_name
= app_item
->name();
79 if (!sync_item
->item_ordinal
.IsValid() ||
80 !app_item
->position().Equals(sync_item
->item_ordinal
)) {
81 sync_item
->item_ordinal
= app_item
->position();
87 void GetSyncSpecificsFromSyncItem(const AppListSyncableService::SyncItem
* item
,
88 sync_pb::AppListSpecifics
* 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
,
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
)
116 #if defined(OS_CHROMEOS)
117 if (id
== file_manager::kFileManagerAppId
|| id
== genius_app::kGeniusAppId
)
123 void UninstallExtension(ExtensionService
* service
, const std::string
& id
) {
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
;
138 LOG(ERROR
) << "Unrecognized model type: " << item_type
;
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
));
161 // AppListSyncableService::SyncItem
163 AppListSyncableService::SyncItem::SyncItem(
164 const std::string
& id
,
165 sync_pb::AppListSpecifics::AppListItemType type
)
170 AppListSyncableService::SyncItem::~SyncItem() {
173 // AppListSyncableService::ModelObserver
175 class AppListSyncableService::ModelObserver
: public AppListModelObserver
{
177 explicit ModelObserver(AppListSyncableService
* owner
)
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";
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
);
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
)
207 owner_
->RemoveSyncItem(item
->id());
210 void OnAppListItemUpdated(AppListItem
* item
) override
{
212 // Adding an item may trigger update notifications which should be
214 DCHECK_EQ(adding_item_
, item
);
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(
231 extensions::ExtensionSystem
* extension_system
)
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";
243 l10n_util::GetStringUTF8(IDS_APP_LIST_OEM_DEFAULT_FOLDER_NAME
);
246 AppListSyncableService::~AppListSyncableService() {
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
);
266 controller
= service
->GetControllerDelegate();
267 apps_builder_
.reset(new ExtensionAppModelBuilder(controller
));
269 if (app_list::switches::IsAppListSyncEnabled()) {
270 VLOG(1) << this << ": AppListSyncableService: InitializeWithService.";
272 apps_builder_
->InitializeWithService(this, model_
.get());
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.
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
);
307 DCHECK_EQ(sync_item
->item_type
,
308 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP
);
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
);
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())
337 void AppListSyncableService::SetOemFolderName(const std::string
& name
) {
338 oem_folder_name_
= name
;
339 AppListFolderItem
* oem_folder
= model_
->FindFolderItem(kOemFolderId
);
341 model_
->SetItemName(oem_folder
, oem_folder_name_
);
344 AppListModel
* AppListSyncableService::GetModel() {
351 void AppListSyncableService::AddItem(scoped_ptr
<AppListItem
> app_item
) {
352 SyncItem
* sync_item
= FindOrAddSyncItem(app_item
.get());
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();
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";
378 SyncItem
* sync_item
= FindSyncItem(item_id
);
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();
387 if (RemoveDefaultApp(app_item
, sync_item
))
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
))
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
);
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
)
415 SyncItem
* sync_item
= FindSyncItem(app_item
->id());
417 UpdateAppItemFromSyncItem(sync_item
, app_item
);
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());
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
);
445 void AppListSyncableService::DeleteSyncItem(SyncItem
* sync_item
) {
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
;
455 sync_items_
.erase(item_id
);
458 void AppListSyncableService::UpdateSyncItem(AppListItem
* app_item
) {
459 SyncItem
* sync_item
= FindSyncItem(app_item
->id());
461 LOG(ERROR
) << "UpdateItem: no sync item: " << app_item
->id();
464 bool changed
= UpdateSyncItemFromAppItem(app_item
, sync_item
);
466 DVLOG(2) << this << " - Update: SYNC NO CHANGE: " << sync_item
->ToString();
469 SendSyncChange(sync_item
, SyncChange::ACTION_UPDATE
);
472 void AppListSyncableService::RemoveItem(const std::string
& id
) {
474 model_
->DeleteItem(id
);
475 PruneEmptySyncFolders();
478 void AppListSyncableService::RemoveUninstalledItem(const std::string
& 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())
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.";
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.";
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
);
523 DeleteSyncItem(sync_item
);
526 void AppListSyncableService::ResolveFolderPositions() {
527 if (!app_list::switches::IsFolderUIEnabled())
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
)
536 AppListItem
* app_item
= model_
->FindItem(sync_item
->item_id
);
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())
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
)
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.
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
))
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.
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));
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
));
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.",
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());
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";
725 SyncItem
* sync_item
= FindSyncItem(item_id
);
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();
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();
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();
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).
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
));
774 UninstallExtension(extension_system_
->extension_service(),
779 case sync_pb::AppListSpecifics::TYPE_FOLDER
: {
780 AppListItem
* app_item
= model_
->FindItem(sync_item
->item_id
);
782 return; // Don't create new folders here, the model will do that.
783 UpdateAppItemFromSyncItem(sync_item
, app_item
);
786 case sync_pb::AppListSpecifics::TYPE_URL
: {
787 // TODO(stevenjb): Implement
788 LOG(WARNING
) << "TYPE_URL not supported";
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
) {
800 VLOG(2) << "ProcessExistingSyncItem: " << sync_item
->ToString();
801 AppListItem
* app_item
= model_
->FindItem(sync_item
->item_id
);
802 DVLOG(2) << " AppItem: " << app_item
->ToDebugString();
804 LOG(ERROR
) << "Item not found in model: " << sync_item
->ToString();
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())
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
);
843 void AppListSyncableService::SendSyncChange(
845 SyncChange::SyncChangeType sync_change_type
) {
846 if (!SyncStarted()) {
847 DVLOG(2) << this << " - SendSyncChange: SYNC NOT STARTED: "
848 << sync_item
->ToString();
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();
861 if (sync_change_type
== SyncChange::ACTION_ADD
)
862 VLOG(2) << this << " -> SYNC ADD: " << sync_item
->ToString();
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())
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
;
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";
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())
900 sync_pb::AppListSpecifics::AppListItemType item_type
=
901 iter
->second
->item_type
;
902 VLOG(2) << this << " <- SYNC DELETE: " << iter
->second
->ToString();
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
);
920 scoped_ptr
<AppListFolderItem
> new_folder(new AppListFolderItem(
921 kOemFolderId
, AppListFolderItem::FOLDER_TYPE_OEM
));
923 static_cast<AppListFolderItem
*>(model_
->AddItem(new_folder
.Pass()));
924 SyncItem
* oem_sync_item
= FindSyncItem(kOemFolderId
);
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
);
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
;
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
)
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());
976 oem_ordinal
= prev
->position().CreateAfter();
978 VLOG(1) << "Placing OEM Folder at: " << oem_index
979 << " position: " << oem_ordinal
.ToDebugString();
983 bool AppListSyncableService::AppIsOem(const std::string
& id
) {
984 if (!extension_system_
->extension_service())
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 }";
996 res
+= " { " + item_name
+ " }";
997 res
+= " [" + item_ordinal
.ToDebugString() + "]";
998 if (!parent_id
.empty())
999 res
+= " <" + parent_id
.substr(0, 8) + ">";
1004 } // namespace app_list