Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / app_list / app_list_syncable_service.cc
index c7d96bd..2dc36f2 100644 (file)
@@ -5,17 +5,22 @@
 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
 
 #include "base/command_line.h"
+#include "chrome/browser/apps/drive/drive_app_provider.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/extension_system.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_service.h"
 #include "chrome/browser/ui/app_list/extension_app_item.h"
 #include "chrome/browser/ui/app_list/extension_app_model_builder.h"
 #include "chrome/browser/ui/host_desktop.h"
 #include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/grit/generated_resources.h"
 #include "content/public/browser/notification_source.h"
 #include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/uninstall_reason.h"
+#include "extensions/common/constants.h"
 #include "sync/api/sync_change_processor.h"
 #include "sync/api/sync_data.h"
 #include "sync/api/sync_merge_result.h"
 #include "ui/app_list/app_list_folder_item.h"
 #include "ui/app_list/app_list_item.h"
 #include "ui/app_list/app_list_model.h"
+#include "ui/app_list/app_list_model_observer.h"
+#include "ui/app_list/app_list_switches.h"
+#include "ui/base/l10n/l10n_util.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/file_manager/app_id.h"
+#include "chrome/browser/chromeos/genius_app/app_id.h"
+#endif
 
 using syncer::SyncChange;
 
@@ -30,10 +43,7 @@ namespace app_list {
 
 namespace {
 
-bool SyncAppListEnabled() {
-  return CommandLine::ForCurrentProcess()->HasSwitch(
-      switches::kEnableSyncAppList);
-}
+const char kOemFolderId[] = "ddb1da55-d478-4243-8642-56d3041f0263";
 
 void UpdateSyncItemFromSync(const sync_pb::AppListSpecifics& specifics,
                             AppListSyncableService::SyncItem* item) {
@@ -51,8 +61,13 @@ bool UpdateSyncItemFromAppItem(const AppListItem* app_item,
                                AppListSyncableService::SyncItem* sync_item) {
   DCHECK_EQ(sync_item->item_id, app_item->id());
   bool changed = false;
-  if (sync_item->item_name != app_item->title()) {
-    sync_item->item_name = app_item->title();
+  if (app_list::switches::IsFolderUIEnabled() &&
+      sync_item->parent_id != app_item->folder_id()) {
+    sync_item->parent_id = app_item->folder_id();
+    changed = true;
+  }
+  if (sync_item->item_name != app_item->name()) {
+    sync_item->item_name = app_item->name();
     changed = true;
   }
   if (!sync_item->item_ordinal.IsValid() ||
@@ -60,7 +75,7 @@ bool UpdateSyncItemFromAppItem(const AppListItem* app_item,
     sync_item->item_ordinal = app_item->position();
     changed = true;
   }
-  // TODO(stevenjb): Set parent_id and page_ordinal.
+  // TODO(stevenjb): Set page_ordinal.
   return changed;
 }
 
@@ -87,12 +102,28 @@ syncer::SyncData GetSyncDataFromSyncItem(
 }
 
 bool AppIsDefault(ExtensionService* service, const std::string& id) {
-  return service && service->extension_prefs()->WasInstalledByDefault(id);
+  return service && extensions::ExtensionPrefs::Get(service->profile())
+                        ->WasInstalledByDefault(id);
+}
+
+bool IsUnRemovableDefaultApp(const std::string& id) {
+  if (id == extension_misc::kChromeAppId ||
+      id == extensions::kWebStoreAppId)
+    return true;
+#if defined(OS_CHROMEOS)
+  if (id == file_manager::kFileManagerAppId || id == genius_app::kGeniusAppId)
+    return true;
+#endif
+  return false;
 }
 
 void UninstallExtension(ExtensionService* service, const std::string& id) {
-  if (service && service->GetInstalledExtension(id))
-    service->UninstallExtension(id, false, NULL);
+  if (service && service->GetInstalledExtension(id)) {
+    service->UninstallExtension(id,
+                                extensions::UNINSTALL_REASON_SYNC,
+                                base::Bind(&base::DoNothing),
+                                NULL);
+  }
 }
 
 bool GetAppListItemType(AppListItem* item,
@@ -123,38 +154,58 @@ AppListSyncableService::SyncItem::SyncItem(
 AppListSyncableService::SyncItem::~SyncItem() {
 }
 
-// AppListSyncableService::ItemListObserver
+// AppListSyncableService::ModelObserver
 
-class AppListSyncableService::ItemListObserver
-    : public AppListItemListObserver {
+class AppListSyncableService::ModelObserver : public AppListModelObserver {
  public:
-  explicit ItemListObserver(AppListSyncableService* owner) : owner_(owner) {
-    owner_->model()->item_list()->AddObserver(this);
+  explicit ModelObserver(AppListSyncableService* owner)
+      : owner_(owner),
+        adding_item_(NULL) {
+    DVLOG(2) << owner_ << ": ModelObserver Added";
+    owner_->model()->AddObserver(this);
   }
 
-  virtual ~ItemListObserver() {
-    owner_->model()->item_list()->RemoveObserver(this);
+  virtual ~ModelObserver() {
+    owner_->model()->RemoveObserver(this);
+    DVLOG(2) << owner_ << ": ModelObserver Removed";
   }
 
  private:
-  // AppListItemListObserver
-  virtual void OnListItemAdded(size_t index, AppListItem* item) OVERRIDE {
+  // AppListModelObserver
+  virtual void OnAppListItemAdded(AppListItem* item) OVERRIDE {
+    DCHECK(!adding_item_);
+    adding_item_ = item;  // Ignore updates while adding an item.
+    VLOG(2) << owner_ << " OnAppListItemAdded: " << item->ToDebugString();
     owner_->AddOrUpdateFromSyncItem(item);
+    adding_item_ = NULL;
   }
 
-  virtual void OnListItemRemoved(size_t index, AppListItem* item) OVERRIDE {
+  virtual void OnAppListItemWillBeDeleted(AppListItem* item) OVERRIDE {
+    DCHECK(!adding_item_);
+    VLOG(2) << owner_ << " OnAppListItemDeleted: " << item->ToDebugString();
+    // Don't sync folder removal in case the folder still exists on another
+    // device (e.g. with device specific items in it). Empty folders will be
+    // deleted when the last item is removed (in PruneEmptySyncFolders()).
+    if (item->GetItemType() == AppListFolderItem::kItemType)
+      return;
     owner_->RemoveSyncItem(item->id());
   }
 
-  virtual void OnListItemMoved(size_t from_index,
-                               size_t to_index,
-                               AppListItem* item) OVERRIDE {
+  virtual void OnAppListItemUpdated(AppListItem* item) OVERRIDE {
+    if (adding_item_) {
+      // Adding an item may trigger update notifications which should be
+      // ignored.
+      DCHECK_EQ(adding_item_, item);
+      return;
+    }
+    VLOG(2) << owner_ << " OnAppListItemUpdated: " << item->ToDebugString();
     owner_->UpdateSyncItem(item);
   }
 
   AppListSyncableService* owner_;
+  AppListItem* adding_item_;  // Unowned pointer to item being added.
 
-  DISALLOW_COPY_AND_ASSIGN(ItemListObserver);
+  DISALLOW_COPY_AND_ASSIGN(ModelObserver);
 };
 
 // AppListSyncableService
@@ -164,28 +215,35 @@ AppListSyncableService::AppListSyncableService(
     extensions::ExtensionSystem* extension_system)
     : profile_(profile),
       extension_system_(extension_system),
-      model_(new AppListModel) {
-  if (!extension_system || !extension_system->extension_service()) {
-    LOG(WARNING) << "AppListSyncableService created with no ExtensionService";
+      model_(new AppListModel),
+      initial_sync_data_processed_(false),
+      first_app_list_sync_(true) {
+  if (!extension_system) {
+    LOG(ERROR) << "AppListSyncableService created with no ExtensionSystem";
     return;
   }
 
-  if (SyncAppListEnabled())
-    item_list_observer_.reset(new ItemListObserver(this));
+  oem_folder_name_ =
+      l10n_util::GetStringUTF8(IDS_APP_LIST_OEM_DEFAULT_FOLDER_NAME);
 
-  if (extension_system->extension_service()->is_ready()) {
+  // Note: model_observer_ is constructed after the initial sync changes are
+  // received in MergeDataAndStartSyncing(). Changes to the model before that
+  // will be synced after the initial sync occurs.
+  if (extension_system->extension_service() &&
+      extension_system->extension_service()->is_ready()) {
     BuildModel();
     return;
   }
 
   // The extensions for this profile have not yet all been loaded.
-  registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
+  registrar_.Add(this,
+                 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
                  content::Source<Profile>(profile));
 }
 
 AppListSyncableService::~AppListSyncableService() {
   // Remove observers.
-  item_list_observer_.reset();
+  model_observer_.reset();
 
   STLDeleteContainerPairSecondPointers(sync_items_.begin(), sync_items_.end());
 }
@@ -202,22 +260,34 @@ void AppListSyncableService::BuildModel() {
     controller = service->GetControllerDelegate();
   apps_builder_.reset(new ExtensionAppModelBuilder(controller));
   DCHECK(profile_);
-  // TODO(stevenjb): Correctly handle OTR profiles for Guest mode.
-  if (!profile_->IsOffTheRecord() && SyncAppListEnabled()) {
-    DVLOG(1) << this << ": AppListSyncableService: InitializeWithService.";
+  if (app_list::switches::IsAppListSyncEnabled()) {
+    VLOG(1) << this << ": AppListSyncableService: InitializeWithService.";
     SyncStarted();
     apps_builder_->InitializeWithService(this);
   } else {
-    DVLOG(1) << this << ": AppListSyncableService: InitializeWithProfile.";
+    VLOG(1) << this << ": AppListSyncableService: InitializeWithProfile.";
     apps_builder_->InitializeWithProfile(profile_, model_.get());
   }
+
+  if (app_list::switches::IsDriveAppsInAppListEnabled())
+    drive_app_provider_.reset(new DriveAppProvider(profile_));
+}
+
+void AppListSyncableService::ResetDriveAppProviderForTest() {
+  drive_app_provider_.reset();
+}
+
+void AppListSyncableService::Shutdown() {
+  // DriveAppProvider touches other KeyedServices in its dtor and needs be
+  // released in shutdown stage.
+  drive_app_provider_.reset();
 }
 
 void AppListSyncableService::Observe(
     int type,
     const content::NotificationSource& source,
     const content::NotificationDetails& details) {
-  DCHECK_EQ(chrome::NOTIFICATION_EXTENSIONS_READY, type);
+  DCHECK_EQ(extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED, type);
   DCHECK_EQ(profile_, content::Source<Profile>(source).ptr());
   registrar_.RemoveAll();
   BuildModel();
@@ -231,21 +301,34 @@ AppListSyncableService::GetSyncItem(const std::string& id) const {
   return NULL;
 }
 
-void AppListSyncableService::AddItem(AppListItem* app_item) {
-  SyncItem* sync_item = AddOrUpdateSyncItem(app_item);
+void AppListSyncableService::SetOemFolderName(const std::string& name) {
+  oem_folder_name_ = name;
+  AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
+  if (oem_folder)
+    model_->SetItemName(oem_folder, oem_folder_name_);
+}
+
+void AppListSyncableService::AddItem(scoped_ptr<AppListItem> app_item) {
+  SyncItem* sync_item = FindOrAddSyncItem(app_item.get());
   if (!sync_item)
     return;  // Item is not valid.
 
-  DVLOG(1) << this << ": AddItem: " << sync_item->ToString();
-
-  // Add the item to the model if necessary.
-  if (!model_->item_list()->FindItem(app_item->id()))
-    model_->item_list()->AddItem(app_item);
-  else
-    model_->item_list()->SetItemPosition(app_item, sync_item->item_ordinal);
+  std::string folder_id;
+  if (app_list::switches::IsFolderUIEnabled()) {
+    if (AppIsOem(app_item->id())) {
+      folder_id = FindOrCreateOemFolder();
+      VLOG_IF(2, !folder_id.empty())
+          << this << ": AddItem to OEM folder: " << sync_item->ToString();
+    } else {
+      folder_id = sync_item->parent_id;
+    }
+  }
+  VLOG(2) << this << ": AddItem: " << sync_item->ToString()
+          << " Folder: '" << folder_id << "'";
+  model_->AddItemToFolder(app_item.Pass(), folder_id);
 }
 
-AppListSyncableService::SyncItem* AppListSyncableService::AddOrUpdateSyncItem(
+AppListSyncableService::SyncItem* AppListSyncableService::FindOrAddSyncItem(
     AppListItem* app_item) {
   const std::string& item_id = app_item->id();
   if (item_id.empty()) {
@@ -254,11 +337,10 @@ AppListSyncableService::SyncItem* AppListSyncableService::AddOrUpdateSyncItem(
   }
   SyncItem* sync_item = FindSyncItem(item_id);
   if (sync_item) {
-    // If there is an existing, non-REMOVE_DEFAULT entry, update it.
+    // If there is an existing, non-REMOVE_DEFAULT entry, return it.
     if (sync_item->item_type !=
         sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
       DVLOG(2) << this << ": AddItem already exists: " << sync_item->ToString();
-      UpdateSyncItem(app_item);
       return sync_item;
     }
 
@@ -277,6 +359,7 @@ AppListSyncableService::CreateSyncItemFromAppItem(AppListItem* app_item) {
   sync_pb::AppListSpecifics::AppListItemType type;
   if (!GetAppListItemType(app_item, &type))
     return NULL;
+  VLOG(2) << this << " CreateSyncItemFromAppItem:" << app_item->ToDebugString();
   SyncItem* sync_item = CreateSyncItem(app_item->id(), type);
   UpdateSyncItemFromAppItem(app_item, sync_item);
   SendSyncChange(sync_item, SyncChange::ACTION_ADD);
@@ -284,6 +367,11 @@ AppListSyncableService::CreateSyncItemFromAppItem(AppListItem* app_item) {
 }
 
 void AppListSyncableService::AddOrUpdateFromSyncItem(AppListItem* app_item) {
+  // Do not create a sync item for the OEM folder here, do that in
+  // ResolveFolderPositions once the position has been resolved.
+  if (app_item->id() == kOemFolderId)
+    return;
+
   SyncItem* sync_item = FindSyncItem(app_item->id());
   if (sync_item) {
     UpdateAppItemFromSyncItem(sync_item, app_item);
@@ -301,8 +389,8 @@ bool AppListSyncableService::RemoveDefaultApp(AppListItem* item,
   // installed as a Default app, uninstall the app instead of adding it.
   if (sync_item->item_type == sync_pb::AppListSpecifics::TYPE_APP &&
       AppIsDefault(extension_system_->extension_service(), item->id())) {
-    DVLOG(1) << this << ": HandleDefaultApp: Uninstall: "
-             << sync_item->ToString();
+    VLOG(2) << this << ": HandleDefaultApp: Uninstall: "
+            << sync_item->ToString();
     UninstallExtension(extension_system_->extension_service(), item->id());
     return true;
   }
@@ -316,7 +404,7 @@ bool AppListSyncableService::RemoveDefaultApp(AppListItem* item,
 
 void AppListSyncableService::DeleteSyncItem(SyncItem* sync_item) {
   if (SyncStarted()) {
-    DVLOG(2) << this << " -> SYNC DELETE: " << sync_item->ToString();
+    VLOG(2) << this << " -> SYNC DELETE: " << sync_item->ToString();
     SyncChange sync_change(FROM_HERE, SyncChange::ACTION_DELETE,
                            GetSyncDataFromSyncItem(sync_item));
     sync_processor_->ProcessSyncChanges(
@@ -343,11 +431,23 @@ void AppListSyncableService::UpdateSyncItem(AppListItem* app_item) {
 
 void AppListSyncableService::RemoveItem(const std::string& id) {
   RemoveSyncItem(id);
-  model_->item_list()->DeleteItem(id);
+  model_->DeleteItem(id);
+  PruneEmptySyncFolders();
+}
+
+void AppListSyncableService::UpdateItem(AppListItem* app_item) {
+  // Check to see if the item needs to be moved to/from the OEM folder.
+  if (!app_list::switches::IsFolderUIEnabled())
+    return;
+  bool is_oem = AppIsOem(app_item->id());
+  if (!is_oem && app_item->folder_id() == kOemFolderId)
+    model_->MoveItemToFolder(app_item, "");
+  else if (is_oem && app_item->folder_id() != kOemFolderId)
+    model_->MoveItemToFolder(app_item, kOemFolderId);
 }
 
 void AppListSyncableService::RemoveSyncItem(const std::string& id) {
-  DVLOG(2) << this << ": RemoveSyncItem: " << id.substr(0, 8);
+  VLOG(2) << this << ": RemoveSyncItem: " << id.substr(0, 8);
   SyncItemMap::iterator iter = sync_items_.find(id);
   if (iter == sync_items_.end()) {
     DVLOG(2) << this << " : RemoveSyncItem: No Item.";
@@ -367,8 +467,8 @@ void AppListSyncableService::RemoveSyncItem(const std::string& id) {
       AppIsDefault(extension_system_->extension_service(), id)) {
     // This is a Default app; update the entry to a REMOVE_DEFAULT entry. This
     // will overwrite any existing entry for the item.
-    DVLOG(2) << this << " -> SYNC UPDATE: REMOVE_DEFAULT: "
-             << sync_item->item_id;
+    VLOG(2) << this << " -> SYNC UPDATE: REMOVE_DEFAULT: "
+            << sync_item->item_id;
     sync_item->item_type = sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP;
     SendSyncChange(sync_item, SyncChange::ACTION_UPDATE);
     return;
@@ -377,6 +477,51 @@ void AppListSyncableService::RemoveSyncItem(const std::string& id) {
   DeleteSyncItem(sync_item);
 }
 
+void AppListSyncableService::ResolveFolderPositions() {
+  if (!app_list::switches::IsFolderUIEnabled())
+    return;
+
+  VLOG(1) << "ResolveFolderPositions.";
+  for (SyncItemMap::iterator iter = sync_items_.begin();
+       iter != sync_items_.end(); ++iter) {
+    SyncItem* sync_item = iter->second;
+    if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
+      continue;
+    AppListItem* app_item = model_->FindItem(sync_item->item_id);
+    if (!app_item)
+      continue;
+    UpdateAppItemFromSyncItem(sync_item, app_item);
+  }
+
+  // Move the OEM folder if one exists and we have not synced its position.
+  AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
+  if (oem_folder && !FindSyncItem(kOemFolderId)) {
+    model_->SetItemPosition(oem_folder, GetOemFolderPos());
+    VLOG(1) << "Creating new OEM folder sync item: "
+            << oem_folder->position().ToDebugString();
+    CreateSyncItemFromAppItem(oem_folder);
+  }
+}
+
+void AppListSyncableService::PruneEmptySyncFolders() {
+  if (!app_list::switches::IsFolderUIEnabled())
+    return;
+
+  std::set<std::string> parent_ids;
+  for (SyncItemMap::iterator iter = sync_items_.begin();
+       iter != sync_items_.end(); ++iter) {
+    parent_ids.insert(iter->second->parent_id);
+  }
+  for (SyncItemMap::iterator iter = sync_items_.begin();
+       iter != sync_items_.end(); ) {
+    SyncItem* sync_item = (iter++)->second;
+    if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
+      continue;
+    if (!ContainsKey(parent_ids, sync_item->item_id))
+      DeleteSyncItem(sync_item);
+  }
+}
+
 // AppListSyncableService syncer::SyncableService
 
 syncer::SyncMergeResult AppListSyncableService::MergeDataAndStartSyncing(
@@ -390,11 +535,13 @@ syncer::SyncMergeResult AppListSyncableService::MergeDataAndStartSyncing(
 
   sync_processor_ = sync_processor.Pass();
   sync_error_handler_ = error_handler.Pass();
+  if (switches::IsFolderUIEnabled())
+    model_->SetFoldersEnabled(true);
 
   syncer::SyncMergeResult result = syncer::SyncMergeResult(type);
   result.set_num_items_before_association(sync_items_.size());
-  DVLOG(1) << this << ": MergeDataAndStartSyncing: "
-           << initial_sync_data.size();
+  VLOG(1) << this << ": MergeDataAndStartSyncing: "
+          << initial_sync_data.size();
 
   // Copy all sync items to |unsynced_items|.
   std::set<std::string> unsynced_items;
@@ -408,33 +555,54 @@ syncer::SyncMergeResult AppListSyncableService::MergeDataAndStartSyncing(
   for (syncer::SyncDataList::const_iterator iter = initial_sync_data.begin();
        iter != initial_sync_data.end(); ++iter) {
     const syncer::SyncData& data = *iter;
-    DVLOG(2) << this << "  Initial Sync Item: "
-             << data.GetSpecifics().app_list().item_id()
-             << " Type: " << data.GetSpecifics().app_list().item_type();
+    const std::string& item_id = data.GetSpecifics().app_list().item_id();
+    const sync_pb::AppListSpecifics& specifics = data.GetSpecifics().app_list();
+    DVLOG(2) << this << "  Initial Sync Item: " << item_id
+             << " Type: " << specifics.item_type();
     DCHECK_EQ(syncer::APP_LIST, data.GetDataType());
-    if (ProcessSyncItemSpecifics(data.GetSpecifics().app_list()))
+    if (ProcessSyncItemSpecifics(specifics))
       ++new_items;
     else
       ++updated_items;
-    unsynced_items.erase(data.GetSpecifics().app_list().item_id());
+    if (specifics.item_type() != sync_pb::AppListSpecifics::TYPE_FOLDER &&
+        !IsUnRemovableDefaultApp(item_id) &&
+        !AppIsOem(item_id) &&
+        !AppIsDefault(extension_system_->extension_service(), item_id)) {
+      VLOG(2) << "Syncing non-default item: " << item_id;
+      first_app_list_sync_ = false;
+    }
+    unsynced_items.erase(item_id);
   }
-
   result.set_num_items_after_association(sync_items_.size());
   result.set_num_items_added(new_items);
   result.set_num_items_deleted(0);
   result.set_num_items_modified(updated_items);
 
+  // Initial sync data has been processed, it is safe now to add new sync items.
+  initial_sync_data_processed_ = true;
+
   // Send unsynced items. Does not affect |result|.
   syncer::SyncChangeList change_list;
   for (std::set<std::string>::iterator iter = unsynced_items.begin();
        iter != unsynced_items.end(); ++iter) {
     SyncItem* sync_item = FindSyncItem(*iter);
-    DVLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
+    // Sync can cause an item to change folders, causing an unsynced folder
+    // item to be removed.
+    if (!sync_item)
+      continue;
+    VLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
     change_list.push_back(SyncChange(FROM_HERE,  SyncChange::ACTION_ADD,
                                      GetSyncDataFromSyncItem(sync_item)));
   }
   sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
 
+  // Adding items may have created folders without setting their positions
+  // since we haven't started observing the item list yet. Resolve those.
+  ResolveFolderPositions();
+
+  // Start observing app list model changes.
+  model_observer_.reset(new ModelObserver(this));
+
   return result;
 }
 
@@ -443,17 +611,18 @@ void AppListSyncableService::StopSyncing(syncer::ModelType type) {
 
   sync_processor_.reset();
   sync_error_handler_.reset();
+  model_->SetFoldersEnabled(false);
 }
 
 syncer::SyncDataList AppListSyncableService::GetAllSyncData(
     syncer::ModelType type) const {
   DCHECK_EQ(syncer::APP_LIST, type);
 
-  DVLOG(1) << this << ": GetAllSyncData: " << sync_items_.size();
+  VLOG(1) << this << ": GetAllSyncData: " << sync_items_.size();
   syncer::SyncDataList list;
   for (SyncItemMap::const_iterator iter = sync_items_.begin();
        iter != sync_items_.end(); ++iter) {
-    DVLOG(2) << this << " -> SYNC: " << iter->second->ToString();
+    VLOG(2) << this << " -> SYNC: " << iter->second->ToString();
     list.push_back(GetSyncDataFromSyncItem(iter->second));
   }
   return list;
@@ -469,13 +638,16 @@ syncer::SyncError AppListSyncableService::ProcessSyncChanges(
                              syncer::APP_LIST);
   }
 
-  DVLOG(1) << this << ": ProcessSyncChanges: " << change_list.size();
+  // Don't observe the model while processing incoming sync changes.
+  model_observer_.reset();
+
+  VLOG(1) << this << ": ProcessSyncChanges: " << change_list.size();
   for (syncer::SyncChangeList::const_iterator iter = change_list.begin();
        iter != change_list.end(); ++iter) {
     const SyncChange& change = *iter;
-    DVLOG(2) << this << "  Change: "
-             << change.sync_data().GetSpecifics().app_list().item_id()
-             << " (" << change.change_type() << ")";
+    VLOG(2) << this << "  Change: "
+            << change.sync_data().GetSpecifics().app_list().item_id()
+            << " (" << change.change_type() << ")";
     if (change.change_type() == SyncChange::ACTION_ADD ||
         change.change_type() == SyncChange::ACTION_UPDATE) {
       ProcessSyncItemSpecifics(change.sync_data().GetSpecifics().app_list());
@@ -485,6 +657,10 @@ syncer::SyncError AppListSyncableService::ProcessSyncChanges(
       LOG(ERROR) << "Invalid sync change";
     }
   }
+
+  // Continue observing app list model changes.
+  model_observer_.reset(new ModelObserver(this));
+
   return syncer::SyncError();
 }
 
@@ -503,7 +679,7 @@ bool AppListSyncableService::ProcessSyncItemSpecifics(
     if (sync_item->item_type == specifics.item_type()) {
       UpdateSyncItemFromSync(specifics, sync_item);
       ProcessExistingSyncItem(sync_item);
-      DVLOG(2) << this << " <- SYNC UPDATE: " << sync_item->ToString();
+      VLOG(2) << this << " <- SYNC UPDATE: " << sync_item->ToString();
       return false;
     }
     // Otherwise, one of the entries should be TYPE_REMOVE_DEFAULT_APP.
@@ -514,10 +690,10 @@ bool AppListSyncableService::ProcessSyncItemSpecifics(
       LOG(ERROR) << "Synced item type: " << specifics.item_type()
                  << " != existing sync item type: " << sync_item->item_type
                  << " Deleting item from model!";
-      model_->item_list()->DeleteItem(item_id);
+      model_->DeleteItem(item_id);
     }
-    DVLOG(2) << this << " - ProcessSyncItem: Delete existing entry: "
-             << sync_item->ToString();
+    VLOG(2) << this << " - ProcessSyncItem: Delete existing entry: "
+            << sync_item->ToString();
     delete sync_item;
     sync_items_.erase(item_id);
   }
@@ -525,12 +701,12 @@ bool AppListSyncableService::ProcessSyncItemSpecifics(
   sync_item = CreateSyncItem(item_id, specifics.item_type());
   UpdateSyncItemFromSync(specifics, sync_item);
   ProcessNewSyncItem(sync_item);
-  DVLOG(2) << this << " <- SYNC ADD: " << sync_item->ToString();
+  VLOG(2) << this << " <- SYNC ADD: " << sync_item->ToString();
   return true;
 }
 
 void AppListSyncableService::ProcessNewSyncItem(SyncItem* sync_item) {
-  DVLOG(2) << "ProcessNewSyncItem: " << sync_item->ToString();
+  VLOG(2) << "ProcessNewSyncItem: " << sync_item->ToString();
   switch (sync_item->item_type) {
     case sync_pb::AppListSpecifics::TYPE_APP: {
       // New apps are added through ExtensionAppModelBuilder.
@@ -539,14 +715,16 @@ void AppListSyncableService::ProcessNewSyncItem(SyncItem* sync_item) {
       return;
     }
     case sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP: {
-      DVLOG(1) << this << ": Uninstall: " << sync_item->ToString();
+      VLOG(1) << this << ": Uninstall: " << sync_item->ToString();
       UninstallExtension(extension_system_->extension_service(),
                          sync_item->item_id);
       return;
     }
     case sync_pb::AppListSpecifics::TYPE_FOLDER: {
-      // TODO(stevenjb): Implement
-      LOG(WARNING) << "TYPE_FOLDER not supported";
+      AppListItem* app_item = model_->FindItem(sync_item->item_id);
+      if (!app_item)
+        return;  // Don't create new folders here, the model will do that.
+      UpdateAppItemFromSyncItem(sync_item, app_item);
       return;
     }
     case sync_pb::AppListSpecifics::TYPE_URL: {
@@ -555,7 +733,7 @@ void AppListSyncableService::ProcessNewSyncItem(SyncItem* sync_item) {
       return;
     }
   }
-  NOTREACHED() << "Unrecoginized sync item type: " << sync_item->ToString();
+  NOTREACHED() << "Unrecognized sync item type: " << sync_item->ToString();
 }
 
 void AppListSyncableService::ProcessExistingSyncItem(SyncItem* sync_item) {
@@ -563,27 +741,43 @@ void AppListSyncableService::ProcessExistingSyncItem(SyncItem* sync_item) {
       sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
     return;
   }
-  DVLOG(2) << "ProcessExistingSyncItem: " << sync_item->ToString();
-  AppListItem* app_item = model_->item_list()->FindItem(sync_item->item_id);
+  VLOG(2) << "ProcessExistingSyncItem: " << sync_item->ToString();
+  AppListItem* app_item = model_->FindItem(sync_item->item_id);
+  DVLOG(2) << " AppItem: " << app_item->ToDebugString();
   if (!app_item) {
     LOG(ERROR) << "Item not found in model: " << sync_item->ToString();
     return;
   }
+  // This is the only place where sync can cause an item to change folders.
+  if (app_list::switches::IsFolderUIEnabled() &&
+      app_item->folder_id() != sync_item->parent_id &&
+      !AppIsOem(app_item->id())) {
+    VLOG(2) << " Moving Item To Folder: " << sync_item->parent_id;
+    model_->MoveItemToFolder(app_item, sync_item->parent_id);
+  }
   UpdateAppItemFromSyncItem(sync_item, app_item);
 }
 
 void AppListSyncableService::UpdateAppItemFromSyncItem(
     const AppListSyncableService::SyncItem* sync_item,
     AppListItem* app_item) {
+  VLOG(2) << this << " UpdateAppItemFromSyncItem: " << sync_item->ToString();
   if (!app_item->position().Equals(sync_item->item_ordinal))
-    model_->item_list()->SetItemPosition(app_item, sync_item->item_ordinal);
+    model_->SetItemPosition(app_item, sync_item->item_ordinal);
+  // Only update the item name if it is a Folder or the name is empty.
+  if (sync_item->item_name != app_item->name() &&
+      sync_item->item_id != kOemFolderId &&
+      (app_item->GetItemType() == AppListFolderItem::kItemType ||
+       app_item->name().empty())) {
+    model_->SetItemName(app_item, sync_item->item_name);
+  }
 }
 
 bool AppListSyncableService::SyncStarted() {
   if (sync_processor_.get())
     return true;
   if (flare_.is_null()) {
-    DVLOG(2) << this << ": SyncStarted: Flare.";
+    VLOG(1) << this << ": SyncStarted: Flare.";
     flare_ = sync_start_util::GetFlareForSyncableService(profile_->GetPath());
     flare_.Run(syncer::APP_LIST);
   }
@@ -598,10 +792,20 @@ void AppListSyncableService::SendSyncChange(
              << sync_item->ToString();
     return;
   }
+  if (!initial_sync_data_processed_ &&
+      sync_change_type == SyncChange::ACTION_ADD) {
+    // This can occur if an initial item is created before its folder item.
+    // A sync item should already exist for the folder, so we do not want to
+    // send an ADD event, since that would trigger a CHECK in the sync code.
+    DCHECK(sync_item->item_type == sync_pb::AppListSpecifics::TYPE_FOLDER);
+    DVLOG(2) << this << " - SendSyncChange: ADD before initial data processed: "
+             << sync_item->ToString();
+    return;
+  }
   if (sync_change_type == SyncChange::ACTION_ADD)
-    DVLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
+    VLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
   else
-    DVLOG(2) << this << " -> SYNC UPDATE: " << sync_item->ToString();
+    VLOG(2) << this << " -> SYNC UPDATE: " << sync_item->ToString();
   SyncChange sync_change(FROM_HERE, sync_change_type,
                          GetSyncDataFromSyncItem(sync_item));
   sync_processor_->ProcessSyncChanges(
@@ -633,17 +837,90 @@ void AppListSyncableService::DeleteSyncItemSpecifics(
     LOG(ERROR) << "Delete AppList item with empty ID";
     return;
   }
-  DVLOG(2) << this << ": DeleteSyncItemSpecifics: " << item_id.substr(0, 8);
+  VLOG(2) << this << ": DeleteSyncItemSpecifics: " << item_id.substr(0, 8);
   SyncItemMap::iterator iter = sync_items_.find(item_id);
   if (iter == sync_items_.end())
     return;
   sync_pb::AppListSpecifics::AppListItemType item_type =
       iter->second->item_type;
-  DVLOG(2) << this << " <- SYNC DELETE: " << iter->second->ToString();
+  VLOG(2) << this << " <- SYNC DELETE: " << iter->second->ToString();
   delete iter->second;
   sync_items_.erase(iter);
-  if (item_type != sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP)
-    model_->item_list()->DeleteItem(item_id);
+  // Only delete apps from the model. Folders will be deleted when all
+  // children have been deleted.
+  if (item_type == sync_pb::AppListSpecifics::TYPE_APP)
+    model_->DeleteItem(item_id);
+}
+
+std::string AppListSyncableService::FindOrCreateOemFolder() {
+  AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
+  if (!oem_folder) {
+    scoped_ptr<AppListFolderItem> new_folder(new AppListFolderItem(
+        kOemFolderId, AppListFolderItem::FOLDER_TYPE_OEM));
+    oem_folder = static_cast<AppListFolderItem*>(
+        model_->AddItem(new_folder.PassAs<app_list::AppListItem>()));
+    SyncItem* oem_sync_item = FindSyncItem(kOemFolderId);
+    if (oem_sync_item) {
+      VLOG(1) << "Creating OEM folder from existing sync item: "
+               << oem_sync_item->item_ordinal.ToDebugString();
+      model_->SetItemPosition(oem_folder, oem_sync_item->item_ordinal);
+    } else {
+      model_->SetItemPosition(oem_folder, GetOemFolderPos());
+      // Do not create a sync item for the OEM folder here, do it in
+      // ResolveFolderPositions() when the item position is finalized.
+    }
+  }
+  model_->SetItemName(oem_folder, oem_folder_name_);
+  return oem_folder->id();
+}
+
+syncer::StringOrdinal AppListSyncableService::GetOemFolderPos() {
+  VLOG(1) << "GetOemFolderPos: " << first_app_list_sync_;
+  if (!first_app_list_sync_) {
+    VLOG(1) << "Sync items exist, placing OEM folder at end.";
+    syncer::StringOrdinal last;
+    for (SyncItemMap::iterator iter = sync_items_.begin();
+         iter != sync_items_.end(); ++iter) {
+      SyncItem* sync_item = iter->second;
+      if (!last.IsValid() || sync_item->item_ordinal.GreaterThan(last))
+        last = sync_item->item_ordinal;
+    }
+    return last.CreateAfter();
+  }
+
+  // Place the OEM folder just after the web store, which should always be
+  // followed by a pre-installed app (e.g. Search), so the poosition should be
+  // stable. TODO(stevenjb): consider explicitly setting the OEM folder location
+  // along with the name in ServicesCustomizationDocument::SetOemFolderName().
+  AppListItemList* item_list = model_->top_level_item_list();
+  if (item_list->item_count() == 0)
+    return syncer::StringOrdinal();
+
+  size_t oem_index = 0;
+  for (; oem_index < item_list->item_count() - 1; ++oem_index) {
+    AppListItem* cur_item = item_list->item_at(oem_index);
+    if (cur_item->id() == extensions::kWebStoreAppId)
+      break;
+  }
+  syncer::StringOrdinal oem_ordinal;
+  AppListItem* prev = item_list->item_at(oem_index);
+  if (oem_index + 1 < item_list->item_count()) {
+    AppListItem* next = item_list->item_at(oem_index + 1);
+    oem_ordinal = prev->position().CreateBetween(next->position());
+  } else {
+    oem_ordinal = prev->position().CreateAfter();
+  }
+  VLOG(1) << "Placing OEM Folder at: " << oem_index
+          << " position: " << oem_ordinal.ToDebugString();
+  return oem_ordinal;
+}
+
+bool AppListSyncableService::AppIsOem(const std::string& id) {
+  if (!extension_system_->extension_service())
+    return false;
+  const extensions::Extension* extension =
+      extension_system_->extension_service()->GetExtensionById(id, true);
+  return extension && extension->was_installed_by_oem();
 }
 
 std::string AppListSyncableService::SyncItem::ToString() const {
@@ -653,6 +930,8 @@ std::string AppListSyncableService::SyncItem::ToString() const {
   } else {
     res += " { " + item_name + " }";
     res += " [" + item_ordinal.ToDebugString() + "]";
+    if (!parent_id.empty())
+      res += " <" + parent_id.substr(0, 8) + ">";
   }
   return res;
 }