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