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 <algorithm>
8 #include <set>
9 #include <utility>
10 #include <vector>
11 
12 #include "base/bind.h"
13 #include "base/command_line.h"
14 #include "base/feature_list.h"
15 #include "base/one_shot_event.h"
16 #include "base/stl_util.h"
17 #include "base/values.h"
18 #include "build/build_config.h"
19 #include "chrome/browser/apps/app_service/app_service_proxy.h"
20 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
21 #include "chrome/browser/chromeos/arc/arc_util.h"
22 #include "chrome/browser/chromeos/crostini/crostini_features.h"
23 #include "chrome/browser/chromeos/crostini/crostini_util.h"
24 #include "chrome/browser/chromeos/file_manager/app_id.h"
25 #include "chrome/browser/chromeos/web_applications/default_web_app_ids.h"
26 #include "chrome/browser/extensions/extension_service.h"
27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/sync/profile_sync_service_factory.h"
29 #include "chrome/browser/ui/app_list/app_list_client_impl.h"
30 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
31 #include "chrome/browser/ui/app_list/app_service/app_service_app_model_builder.h"
32 #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
33 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
34 #include "chrome/browser/ui/app_list/chrome_app_list_item.h"
35 #include "chrome/browser/ui/app_list/chrome_app_list_model_updater.h"
36 #include "chrome/browser/ui/app_list/page_break_app_item.h"
37 #include "chrome/browser/ui/app_list/page_break_constants.h"
38 #include "chrome/common/chrome_switches.h"
39 #include "chrome/common/extensions/extension_constants.h"
40 #include "chrome/common/pref_names.h"
41 #include "chrome/grit/generated_resources.h"
42 #include "chromeos/constants/chromeos_switches.h"
43 #include "components/pref_registry/pref_registry_syncable.h"
44 #include "components/sync/driver/profile_sync_service.h"
45 #include "components/sync/model/sync_change_processor.h"
46 #include "components/sync/model/sync_data.h"
47 #include "components/sync/protocol/sync.pb.h"
48 #include "extensions/browser/extension_prefs.h"
49 #include "extensions/browser/extension_registry.h"
50 #include "extensions/browser/extension_system.h"
51 #include "extensions/browser/uninstall_reason.h"
52 #include "extensions/common/constants.h"
53 #include "ui/base/l10n/l10n_util.h"
54 
55 using syncer::SyncChange;
56 
57 namespace app_list {
58 
59 namespace {
60 
61 constexpr char kNameKey[] = "name";
62 constexpr char kParentIdKey[] = "parent_id";
63 constexpr char kPositionKey[] = "position";
64 constexpr char kPinPositionKey[] = "pin_position";
65 constexpr char kTypeKey[] = "type";
66 
GetSyncSpecificsFromSyncItem(const AppListSyncableService::SyncItem * item,sync_pb::AppListSpecifics * specifics)67 void GetSyncSpecificsFromSyncItem(const AppListSyncableService::SyncItem* item,
68                                   sync_pb::AppListSpecifics* specifics) {
69   DCHECK(specifics);
70   specifics->set_item_id(item->item_id);
71   specifics->set_item_type(item->item_type);
72   specifics->set_item_name(item->item_name);
73   specifics->set_parent_id(item->parent_id);
74   specifics->set_item_ordinal(item->item_ordinal.IsValid()
75                                   ? item->item_ordinal.ToInternalValue()
76                                   : std::string());
77   specifics->set_item_pin_ordinal(item->item_pin_ordinal.IsValid()
78                                       ? item->item_pin_ordinal.ToInternalValue()
79                                       : std::string());
80 }
81 
GetSyncDataFromSyncItem(const AppListSyncableService::SyncItem * item)82 syncer::SyncData GetSyncDataFromSyncItem(
83     const AppListSyncableService::SyncItem* item) {
84   sync_pb::EntitySpecifics specifics;
85   GetSyncSpecificsFromSyncItem(item, specifics.mutable_app_list());
86   return syncer::SyncData::CreateLocalData(item->item_id, item->item_id,
87                                            specifics);
88 }
89 
AppIsDefault(Profile * profile,const std::string & id)90 bool AppIsDefault(Profile* profile, const std::string& id) {
91   // Querying the extension system is legacy logic from the time that we only
92   // had extension apps.
93   if (extensions::ExtensionPrefs::Get(profile)->WasInstalledByDefault(id))
94     return true;
95 
96   bool result = false;
97   apps::AppServiceProxyFactory::GetForProfile(profile)
98       ->AppRegistryCache()
99       .ForOneApp(id, [&result](const apps::AppUpdate& update) {
100         result = update.InstallSource() == apps::mojom::InstallSource::kDefault;
101       });
102   return result;
103 }
104 
SetAppIsDefaultForTest(Profile * profile,const std::string & id)105 void SetAppIsDefaultForTest(Profile* profile, const std::string& id) {
106   apps::mojom::AppPtr delta = apps::mojom::App::New();
107   delta->app_type = apps::mojom::AppType::kExtension;
108   delta->app_id = id;
109   delta->install_source = apps::mojom::InstallSource::kDefault;
110 
111   std::vector<apps::mojom::AppPtr> deltas;
112   deltas.push_back(std::move(delta));
113 
114   apps::AppServiceProxyFactory::GetForProfile(profile)
115       ->AppRegistryCache()
116       .OnApps(std::move(deltas));
117 }
118 
IsUnRemovableDefaultApp(const std::string & id)119 bool IsUnRemovableDefaultApp(const std::string& id) {
120   return id == extension_misc::kChromeAppId ||
121          id == extensions::kWebStoreAppId ||
122          id == file_manager::kFileManagerAppId;
123 }
124 
UninstallExtension(extensions::ExtensionService * service,extensions::ExtensionRegistry * registry,const std::string & id)125 void UninstallExtension(extensions::ExtensionService* service,
126                         extensions::ExtensionRegistry* registry,
127                         const std::string& id) {
128   if (service && registry->GetInstalledExtension(id)) {
129     service->UninstallExtension(id, extensions::UNINSTALL_REASON_SYNC,
130                                 nullptr /* error */);
131   }
132 }
133 
GetAppListItemType(const ChromeAppListItem * item)134 sync_pb::AppListSpecifics::AppListItemType GetAppListItemType(
135     const ChromeAppListItem* item) {
136   if (item->is_folder())
137     return sync_pb::AppListSpecifics::TYPE_FOLDER;
138   else if (item->is_page_break())
139     return sync_pb::AppListSpecifics::TYPE_PAGE_BREAK;
140   else
141     return sync_pb::AppListSpecifics::TYPE_APP;
142 }
143 
RemoveSyncItemFromLocalStorage(Profile * profile,const std::string & item_id)144 void RemoveSyncItemFromLocalStorage(Profile* profile,
145                                     const std::string& item_id) {
146   DictionaryPrefUpdate(profile->GetPrefs(), prefs::kAppListLocalState)
147       ->Remove(item_id, nullptr);
148 }
149 
UpdateSyncItemInLocalStorage(Profile * profile,const AppListSyncableService::SyncItem * sync_item)150 void UpdateSyncItemInLocalStorage(
151     Profile* profile,
152     const AppListSyncableService::SyncItem* sync_item) {
153   DictionaryPrefUpdate pref_update(profile->GetPrefs(),
154                                    prefs::kAppListLocalState);
155   base::Value* dict_item = pref_update->FindKeyOfType(
156       sync_item->item_id, base::Value::Type::DICTIONARY);
157   if (!dict_item) {
158     dict_item = pref_update->SetKey(sync_item->item_id,
159                                     base::Value(base::Value::Type::DICTIONARY));
160   }
161 
162   dict_item->SetKey(kNameKey, base::Value(sync_item->item_name));
163   dict_item->SetKey(kParentIdKey, base::Value(sync_item->parent_id));
164   dict_item->SetKey(kPositionKey,
165                     base::Value(sync_item->item_ordinal.IsValid()
166                                     ? sync_item->item_ordinal.ToInternalValue()
167                                     : std::string()));
168   dict_item->SetKey(
169       kPinPositionKey,
170       base::Value(sync_item->item_pin_ordinal.IsValid()
171                       ? sync_item->item_pin_ordinal.ToInternalValue()
172                       : std::string()));
173   dict_item->SetKey(kTypeKey,
174                     base::Value(static_cast<int>(sync_item->item_type)));
175 }
176 
177 AppListSyncableService::ModelUpdaterFactoryCallback*
178     g_model_updater_factory_callback_for_test_ = nullptr;
179 
180 // Returns true if the sync item does not have parent.
IsTopLevelAppItem(const AppListSyncableService::SyncItem & sync_item)181 bool IsTopLevelAppItem(const AppListSyncableService::SyncItem& sync_item) {
182   return sync_item.parent_id.empty();
183 }
184 
185 // Returns true if the sync item is a page break item.
IsPageBreakItem(const AppListSyncableService::SyncItem & sync_item)186 bool IsPageBreakItem(const AppListSyncableService::SyncItem& sync_item) {
187   return sync_item.item_type == sync_pb::AppListSpecifics::TYPE_PAGE_BREAK;
188 }
189 
190 // Returns true if the app is Settings app
IsOsSettingsApp(const std::string & app_id)191 bool IsOsSettingsApp(const std::string& app_id) {
192   return app_id == chromeos::default_web_apps::kOsSettingsAppId;
193 }
194 
IsSystemCreatedSyncFolder(AppListSyncableService::SyncItem * folder_item)195 bool IsSystemCreatedSyncFolder(AppListSyncableService::SyncItem* folder_item) {
196   if (folder_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
197     return false;
198   return (folder_item->item_id == ash::kOemFolderId ||
199           folder_item->item_id == crostini::kCrostiniFolderId);
200 }
201 
202 }  // namespace
203 
204 // AppListSyncableService::ScopedModelUpdaterFactoryForTest
205 
206 AppListSyncableService::ScopedModelUpdaterFactoryForTest::
ScopedModelUpdaterFactoryForTest(const ModelUpdaterFactoryCallback & factory)207     ScopedModelUpdaterFactoryForTest(
208         const ModelUpdaterFactoryCallback& factory) {
209   DCHECK(factory);
210   factory_ = factory;
211   g_model_updater_factory_callback_for_test_ = &factory_;
212 }
213 
214 AppListSyncableService::ScopedModelUpdaterFactoryForTest::
~ScopedModelUpdaterFactoryForTest()215     ~ScopedModelUpdaterFactoryForTest() {
216   DCHECK_EQ(&factory_, g_model_updater_factory_callback_for_test_);
217   g_model_updater_factory_callback_for_test_ = nullptr;
218 }
219 
220 // AppListSyncableService::SyncItem
221 
SyncItem(const std::string & id,sync_pb::AppListSpecifics::AppListItemType type)222 AppListSyncableService::SyncItem::SyncItem(
223     const std::string& id,
224     sync_pb::AppListSpecifics::AppListItemType type)
225     : item_id(id), item_type(type) {}
226 
227 AppListSyncableService::SyncItem::~SyncItem() = default;
228 
229 // AppListSyncableService::ModelUpdaterObserver
230 
231 class AppListSyncableService::ModelUpdaterObserver
232     : public AppListModelUpdaterObserver {
233  public:
ModelUpdaterObserver(AppListSyncableService * owner)234   explicit ModelUpdaterObserver(AppListSyncableService* owner) : owner_(owner) {
235     DVLOG(2) << owner_ << ": ModelUpdaterObserver Added";
236     owner_->GetModelUpdater()->AddObserver(this);
237   }
238   ModelUpdaterObserver(const ModelUpdaterObserver&) = delete;
239   ModelUpdaterObserver& operator=(const ModelUpdaterObserver&) = delete;
~ModelUpdaterObserver()240   ~ModelUpdaterObserver() override {
241     owner_->GetModelUpdater()->RemoveObserver(this);
242     DVLOG(2) << owner_ << ": ModelUpdaterObserver Removed";
243   }
244 
245  private:
246   // ChromeAppListModelUpdaterObserver
OnAppListItemAdded(ChromeAppListItem * item)247   void OnAppListItemAdded(ChromeAppListItem* item) override {
248     // Only sync folders and page breaks which are added from Ash.
249     if (!item->is_folder() && !item->is_page_break())
250       return;
251     DCHECK(adding_item_id_.empty());
252     adding_item_id_ = item->id();  // Ignore updates while adding an item.
253     VLOG(2) << owner_ << " OnAppListItemAdded: " << item->ToDebugString();
254     owner_->AddOrUpdateFromSyncItem(item);
255     adding_item_id_.clear();
256 
257     // Sync OEM name if it was created on demand on ash side.
258     if (item->id() == ash::kOemFolderId &&
259         item->name() != owner_->oem_folder_name_) {
260       item->SetName(owner_->oem_folder_name_);
261     }
262   }
263 
OnAppListItemWillBeDeleted(ChromeAppListItem * item)264   void OnAppListItemWillBeDeleted(ChromeAppListItem* item) override {
265     DCHECK(adding_item_id_.empty());
266     VLOG(2) << owner_ << " OnAppListItemDeleted: " << item->ToDebugString();
267     // Don't sync folder removal in case the folder still exists on another
268     // device (e.g. with device specific items in it). Empty folders will be
269     // deleted when the last item is removed (in PruneEmptySyncFolders()).
270     if (item->is_folder())
271       return;
272 
273     owner_->RemoveSyncItem(item->id());
274   }
275 
OnAppListItemUpdated(ChromeAppListItem * item)276   void OnAppListItemUpdated(ChromeAppListItem* item) override {
277     if (!adding_item_id_.empty()) {
278       // Adding an item may trigger update notifications which should be
279       // ignored.
280       DCHECK_EQ(adding_item_id_, item->id());
281       return;
282     }
283     VLOG(2) << owner_ << " OnAppListItemUpdated: " << item->ToDebugString();
284     owner_->UpdateSyncItem(item);
285   }
286 
287   AppListSyncableService* owner_;
288   std::string adding_item_id_;
289 };
290 
291 // AppListSyncableService
292 
293 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)294 void AppListSyncableService::RegisterProfilePrefs(
295     user_prefs::PrefRegistrySyncable* registry) {
296   registry->RegisterDictionaryPref(prefs::kAppListLocalState);
297 }
298 
299 // static
AppIsDefaultForTest(Profile * profile,const std::string & id)300 bool AppListSyncableService::AppIsDefaultForTest(Profile* profile,
301                                                  const std::string& id) {
302   return AppIsDefault(profile, id);
303 }
304 
305 // static
SetAppIsDefaultForTest(Profile * profile,const std::string & id)306 void AppListSyncableService::SetAppIsDefaultForTest(Profile* profile,
307                                                     const std::string& id) {
308   app_list::SetAppIsDefaultForTest(profile, id);
309 }
310 
AppListSyncableService(Profile * profile)311 AppListSyncableService::AppListSyncableService(Profile* profile)
312     : profile_(profile),
313       extension_system_(extensions::ExtensionSystem::Get(profile)),
314       extension_registry_(extensions::ExtensionRegistry::Get(profile)) {
315   if (g_model_updater_factory_callback_for_test_)
316     model_updater_ = g_model_updater_factory_callback_for_test_->Run();
317   else
318     model_updater_ = std::make_unique<ChromeAppListModelUpdater>(profile);
319 
320   if (!extension_system_) {
321     LOG(ERROR) << "AppListSyncableService created with no ExtensionSystem";
322     return;
323   }
324 
325   oem_folder_name_ =
326       l10n_util::GetStringUTF8(IDS_APP_LIST_OEM_DEFAULT_FOLDER_NAME);
327 
328   if (IsExtensionServiceReady()) {
329     BuildModel();
330   } else {
331     extension_system_->ready().Post(
332         FROM_HERE, base::BindOnce(&AppListSyncableService::BuildModel,
333                                   weak_ptr_factory_.GetWeakPtr()));
334   }
335 }
336 
~AppListSyncableService()337 AppListSyncableService::~AppListSyncableService() {
338   // Remove observers.
339   model_updater_observer_.reset();
340 }
341 
IsExtensionServiceReady() const342 bool AppListSyncableService::IsExtensionServiceReady() const {
343   return extension_system_->is_ready();
344 }
345 
InitFromLocalStorage()346 void AppListSyncableService::InitFromLocalStorage() {
347   // This should happen before sync and model is built.
348   DCHECK(!sync_processor_.get());
349   DCHECK(!IsInitialized());
350 
351   // Restore initial state from local storage.
352   const base::DictionaryValue* local_items =
353       profile_->GetPrefs()->GetDictionary(prefs::kAppListLocalState);
354   DCHECK(local_items);
355 
356   for (base::DictionaryValue::Iterator item(*local_items); !item.IsAtEnd();
357        item.Advance()) {
358     const base::DictionaryValue* dict_item;
359     if (!item.value().GetAsDictionary(&dict_item)) {
360       LOG(ERROR) << "Dictionary not found for " << item.key() + ".";
361       continue;
362     }
363 
364     int type;
365     if (!dict_item->GetInteger(kTypeKey, &type)) {
366       LOG(ERROR) << "Item type is not set in local storage for " << item.key()
367                  << ".";
368       continue;
369     }
370 
371     SyncItem* sync_item = CreateSyncItem(
372         item.key(),
373         static_cast<sync_pb::AppListSpecifics::AppListItemType>(type));
374 
375     dict_item->GetString(kNameKey, &sync_item->item_name);
376     dict_item->GetString(kParentIdKey, &sync_item->parent_id);
377     std::string position;
378     std::string pin_position;
379     dict_item->GetString(kPositionKey, &position);
380     dict_item->GetString(kPinPositionKey, &pin_position);
381     if (!position.empty())
382       sync_item->item_ordinal = syncer::StringOrdinal(position);
383     if (!pin_position.empty())
384       sync_item->item_pin_ordinal = syncer::StringOrdinal(pin_position);
385     ProcessNewSyncItem(sync_item);
386   }
387 }
388 
IsInitialized() const389 bool AppListSyncableService::IsInitialized() const {
390   return app_service_apps_builder_.get();
391 }
392 
IsSyncing() const393 bool AppListSyncableService::IsSyncing() const {
394   return sync_processor_.get();
395 }
396 
BuildModel()397 void AppListSyncableService::BuildModel() {
398   InitFromLocalStorage();
399 
400   DCHECK(IsExtensionServiceReady());
401   AppListClientImpl* client = AppListClientImpl::GetInstance();
402   AppListControllerDelegate* controller = client;
403 
404   app_service_apps_builder_ =
405       std::make_unique<AppServiceAppModelBuilder>(controller);
406 
407   DCHECK(profile_);
408   SyncStarted();
409 
410   app_service_apps_builder_->Initialize(this, profile_, model_updater_.get());
411 
412   HandleUpdateFinished(false /* clean_up_after_init_sync */);
413 
414   if (wait_until_ready_to_sync_cb_)
415     std::move(wait_until_ready_to_sync_cb_).Run();
416 
417   // Install default page brakes for tablet form factor devices here as
418   // these devices do not have app list sync turned on.
419   if (chromeos::switches::IsTabletFormFactor() && profile_->IsNewProfile()) {
420     DCHECK(!ProfileSyncServiceFactory::GetForProfile(profile_)
421                 ->GetActiveDataTypes()
422                 .Has(syncer::APP_LIST));
423     // Create call back to create the default page break items at later time so
424     // that default page break items are not removed by
425     // |PruneRedundantPageBreakItems|
426     install_default_page_breaks_ =
427         base::BindOnce(&AppListSyncableService::InstallDefaultPageBreaks,
428                        weak_ptr_factory_.GetWeakPtr());
429   }
430 }
431 
AddObserverAndStart(Observer * observer)432 void AppListSyncableService::AddObserverAndStart(Observer* observer) {
433   observer_list_.AddObserver(observer);
434   SyncStarted();
435 }
436 
RemoveObserver(Observer * observer)437 void AppListSyncableService::RemoveObserver(Observer* observer) {
438   observer_list_.RemoveObserver(observer);
439 }
440 
NotifyObserversSyncUpdated()441 void AppListSyncableService::NotifyObserversSyncUpdated() {
442   for (auto& observer : observer_list_)
443     observer.OnSyncModelUpdated();
444 }
445 
GetNumSyncItemsForTest()446 size_t AppListSyncableService::GetNumSyncItemsForTest() {
447   DCHECK(IsInitialized());
448   return sync_items_.size();
449 }
450 
GetSyncItem(const std::string & id) const451 const AppListSyncableService::SyncItem* AppListSyncableService::GetSyncItem(
452     const std::string& id) const {
453   auto iter = sync_items_.find(id);
454   if (iter != sync_items_.end())
455     return iter->second.get();
456   return NULL;
457 }
458 
TransferItemAttributes(const std::string & from_app_id,const std::string & to_app_id)459 bool AppListSyncableService::TransferItemAttributes(
460     const std::string& from_app_id,
461     const std::string& to_app_id) {
462   const SyncItem* from_item = FindSyncItem(from_app_id);
463   if (!from_item ||
464       from_item->item_type != sync_pb::AppListSpecifics::TYPE_APP) {
465     return false;
466   }
467 
468   auto attributes = std::make_unique<SyncItem>(
469       from_app_id, sync_pb::AppListSpecifics::TYPE_APP);
470   attributes->parent_id = from_item->parent_id;
471   attributes->item_ordinal = from_item->item_ordinal;
472   attributes->item_pin_ordinal = from_item->item_pin_ordinal;
473 
474   SyncItem* to_item = FindSyncItem(to_app_id);
475   if (to_item) {
476     // |to_app_id| already exists. Can apply attributes right now.
477     ApplyAppAttributes(to_app_id, std::move(attributes));
478   } else {
479     // |to_app_id| does not exist at this moment. Store attributes to apply it
480     // later once app appears on this device.
481     pending_transfer_map_[to_app_id] = std::move(attributes);
482   }
483 
484   return true;
485 }
486 
ApplyAppAttributes(const std::string & app_id,std::unique_ptr<SyncItem> attributes)487 void AppListSyncableService::ApplyAppAttributes(
488     const std::string& app_id,
489     std::unique_ptr<SyncItem> attributes) {
490   SyncItem* item = FindSyncItem(app_id);
491   if (!item || item->item_type != sync_pb::AppListSpecifics::TYPE_APP) {
492     LOG(ERROR) << "Failed to apply app attributes, app " << app_id
493                << " does not exist.";
494     return;
495   }
496 
497   HandleUpdateStarted();
498 
499   item->parent_id = attributes->parent_id;
500   item->item_ordinal = attributes->item_ordinal;
501   item->item_pin_ordinal = attributes->item_pin_ordinal;
502   UpdateSyncItemInLocalStorage(profile_, item);
503   SendSyncChange(item, SyncChange::ACTION_UPDATE);
504   ProcessExistingSyncItem(item);
505 
506   HandleUpdateFinished(false /* clean_up_after_init_sync */);
507 }
508 
SetOemFolderName(const std::string & name)509 void AppListSyncableService::SetOemFolderName(const std::string& name) {
510   oem_folder_name_ = name;
511   // Update OEM folder item if it was already created. If it is not created yet
512   // then on creation it will take right name.
513   ChromeAppListItem* oem_folder_item =
514       model_updater_->FindItem(ash::kOemFolderId);
515   if (oem_folder_item)
516     oem_folder_item->SetName(oem_folder_name_);
517 }
518 
GetModelUpdater()519 AppListModelUpdater* AppListSyncableService::GetModelUpdater() {
520   return model_updater_.get();
521 }
522 
HandleUpdateStarted()523 void AppListSyncableService::HandleUpdateStarted() {
524   // Don't observe the model while processing update changes.
525   model_updater_observer_.reset();
526 }
527 
HandleUpdateFinished(bool clean_up_after_init_sync)528 void AppListSyncableService::HandleUpdateFinished(
529     bool clean_up_after_init_sync) {
530   // Processing an update may create folders without setting their positions.
531   // Resolve them now.
532   ResolveFolderPositions();
533 
534   if (clean_up_after_init_sync) {
535     PruneEmptySyncFolders();
536     CleanUpSingleItemSyncFolder();
537   }
538 
539   // Resume or start observing app list model changes.
540   model_updater_observer_ = std::make_unique<ModelUpdaterObserver>(this);
541 
542   NotifyObserversSyncUpdated();
543 }
544 
CleanUpSingleItemSyncFolder()545 void AppListSyncableService::CleanUpSingleItemSyncFolder() {
546   std::vector<std::string> ids_to_be_deleted;
547   for (auto iter = sync_items_.begin(); iter != sync_items_.end();) {
548     SyncItem* sync_item = (iter++)->second.get();
549     if (RemoveOnlyChildOutOfUserCreatedFolderIfNecessary(sync_item)) {
550       // Remember the id of the folder item to be deleted.
551       ids_to_be_deleted.push_back(sync_item->item_id);
552     }
553   }
554 
555   // Remove the empty folder items.
556   for (auto id : ids_to_be_deleted)
557     DeleteSyncItem(id);
558 }
559 
560 AppListSyncableService::SyncItem*
GetOnlyChildOfUserCreatedFolder(SyncItem * sync_item)561 AppListSyncableService::GetOnlyChildOfUserCreatedFolder(SyncItem* sync_item) {
562   if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER ||
563       IsSystemCreatedSyncFolder(sync_item))
564     return nullptr;
565 
566   const std::string& folder_id = sync_item->item_id;
567   int child_count = 0;
568   SyncItem* child_item = nullptr;
569   for (auto iter = sync_items_.begin(); iter != sync_items_.end();) {
570     SyncItem* item = (iter++)->second.get();
571     if (item->parent_id == folder_id) {
572       ++child_count;
573       child_item = item;
574       if (child_count > 1)
575         return nullptr;
576     }
577   }
578   return child_item;
579 }
580 
RemoveOnlyChildOutOfUserCreatedFolderIfNecessary(SyncItem * sync_item)581 bool AppListSyncableService::RemoveOnlyChildOutOfUserCreatedFolderIfNecessary(
582     SyncItem* sync_item) {
583   SyncItem* child_item = GetOnlyChildOfUserCreatedFolder(sync_item);
584   if (!child_item)
585     return false;
586 
587   // Move the single child item out of folder and put at the same relative
588   // location as the folder.
589   child_item->item_ordinal = sync_item->item_ordinal;
590   child_item->parent_id = "";
591   UpdateSyncItemInLocalStorage(profile_, child_item);
592   SendSyncChange(child_item, SyncChange::ACTION_UPDATE);
593   // Update the app list model updater for the sync change.
594   ProcessExistingSyncItem(child_item);
595   return true;
596 }
597 
AddItem(std::unique_ptr<ChromeAppListItem> app_item)598 void AppListSyncableService::AddItem(
599     std::unique_ptr<ChromeAppListItem> app_item) {
600   SyncItem* sync_item = FindOrAddSyncItem(app_item.get());
601   if (!sync_item)
602     return;  // Item is not valid.
603 
604   if (AppIsOem(app_item->id())) {
605     VLOG(2) << this << ": AddItem to OEM folder: " << sync_item->ToString();
606     model_updater_->AddItemToOemFolder(
607         std::move(app_item), FindSyncItem(ash::kOemFolderId), oem_folder_name_,
608         GetPreferredOemFolderPos());
609   } else {
610     std::string folder_id = sync_item->parent_id;
611     VLOG(2) << this << ": AddItem: " << sync_item->ToString() << " Folder: '"
612             << folder_id << "'";
613     model_updater_->AddItemToFolder(std::move(app_item), folder_id);
614   }
615 
616   // Calculate this early since |sync_item| could be deleted in
617   // PruneRedundantPageBreakItems.
618   bool run_install_default_page_breaks =
619       install_default_page_breaks_ && IsOsSettingsApp(sync_item->item_id);
620   PruneRedundantPageBreakItems();
621 
622   if (run_install_default_page_breaks)
623     std::move(install_default_page_breaks_).Run();
624 }
625 
FindOrAddSyncItem(const ChromeAppListItem * app_item)626 AppListSyncableService::SyncItem* AppListSyncableService::FindOrAddSyncItem(
627     const ChromeAppListItem* app_item) {
628   const std::string& item_id = app_item->id();
629   if (item_id.empty()) {
630     LOG(ERROR) << "ChromeAppListItem item with empty ID";
631     return NULL;
632   }
633   SyncItem* sync_item = FindSyncItem(item_id);
634   if (sync_item) {
635     // If there is an existing, non-REMOVE_DEFAULT entry, return it.
636     if (sync_item->item_type !=
637         sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
638       DVLOG(2) << this << ": AddItem already exists: " << sync_item->ToString();
639       return sync_item;
640     }
641 
642     if (RemoveDefaultApp(app_item, sync_item))
643       return NULL;
644 
645     // Fall through. The REMOVE_DEFAULT_APP entry has been deleted, now a new
646     // App entry can be added.
647   }
648 
649   return CreateSyncItemFromAppItem(app_item);
650 }
651 
652 AppListSyncableService::SyncItem*
CreateSyncItemFromAppItem(const ChromeAppListItem * app_item)653 AppListSyncableService::CreateSyncItemFromAppItem(
654     const ChromeAppListItem* app_item) {
655   sync_pb::AppListSpecifics::AppListItemType type =
656       GetAppListItemType(app_item);
657   VLOG(2) << this << " CreateSyncItemFromAppItem:" << app_item->ToDebugString();
658   SyncItem* sync_item = CreateSyncItem(app_item->id(), type);
659   DCHECK(app_item->position().IsValid());
660   UpdateSyncItemFromAppItem(app_item, sync_item);
661   UpdateSyncItemInLocalStorage(profile_, sync_item);
662   SendSyncChange(sync_item, SyncChange::ACTION_ADD);
663   return sync_item;
664 }
665 
GetPinPosition(const std::string & app_id)666 syncer::StringOrdinal AppListSyncableService::GetPinPosition(
667     const std::string& app_id) {
668   SyncItem* sync_item = FindSyncItem(app_id);
669   if (!sync_item)
670     return syncer::StringOrdinal();
671   return sync_item->item_pin_ordinal;
672 }
673 
SetPinPosition(const std::string & app_id,const syncer::StringOrdinal & item_pin_ordinal)674 void AppListSyncableService::SetPinPosition(
675     const std::string& app_id,
676     const syncer::StringOrdinal& item_pin_ordinal) {
677   // Pin position can be set only after model is built.
678   DCHECK(IsInitialized());
679   SyncItem* sync_item = FindSyncItem(app_id);
680   SyncChange::SyncChangeType sync_change_type;
681   if (sync_item) {
682     sync_change_type = SyncChange::ACTION_UPDATE;
683   } else {
684     sync_item = CreateSyncItem(app_id, sync_pb::AppListSpecifics::TYPE_APP);
685     sync_change_type = SyncChange::ACTION_ADD;
686   }
687 
688   sync_item->item_pin_ordinal = item_pin_ordinal;
689   UpdateSyncItemInLocalStorage(profile_, sync_item);
690   SendSyncChange(sync_item, sync_change_type);
691 }
692 
AddOrUpdateFromSyncItem(const ChromeAppListItem * app_item)693 void AppListSyncableService::AddOrUpdateFromSyncItem(
694     const ChromeAppListItem* app_item) {
695   // Do not create a sync item for the OEM folder here, do that in
696   // ResolveFolderPositions once the position has been resolved.
697   if (app_item->id() == ash::kOemFolderId)
698     return;
699 
700   DCHECK(app_item->position().IsValid());
701 
702   SyncItem* sync_item = FindSyncItem(app_item->id());
703   if (sync_item) {
704     model_updater_->UpdateAppItemFromSyncItem(
705         sync_item,
706         sync_item->item_id !=
707             ash::kOemFolderId,  // Don't sync oem folder's name.
708         false);                 // Don't sync its folder here.
709     if (!sync_item->item_ordinal.IsValid()) {
710       UpdateSyncItem(app_item);
711       VLOG(2) << "Flushing position to sync item " << sync_item;
712     }
713     return;
714   }
715   CreateSyncItemFromAppItem(app_item);
716 }
717 
RemoveDefaultApp(const ChromeAppListItem * item,SyncItem * sync_item)718 bool AppListSyncableService::RemoveDefaultApp(const ChromeAppListItem* item,
719                                               SyncItem* sync_item) {
720   CHECK_EQ(sync_item->item_type,
721            sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP);
722 
723   // If there is an existing REMOVE_DEFAULT_APP entry, and the app is
724   // installed as a Default app, uninstall the app instead of adding it.
725   if (sync_item->item_type == sync_pb::AppListSpecifics::TYPE_APP &&
726       AppIsDefault(profile_, item->id())) {
727     VLOG(2) << this
728             << ": HandleDefaultApp: Uninstall: " << sync_item->ToString();
729     UninstallExtension(extension_system_->extension_service(),
730                        extension_registry_, item->id());
731     return true;
732   }
733 
734   // Otherwise, we are adding the app as a non-default app (i.e. an app that
735   // was installed by Default and removed is getting installed explicitly by
736   // the user), so delete the REMOVE_DEFAULT_APP.
737   DeleteSyncItem(sync_item->item_id);
738   return false;
739 }
740 
InterceptDeleteDefaultApp(SyncItem * sync_item)741 bool AppListSyncableService::InterceptDeleteDefaultApp(SyncItem* sync_item) {
742   if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_APP ||
743       !AppIsDefault(profile_, sync_item->item_id)) {
744     return false;
745   }
746 
747   // This is a Default app; update the entry to a REMOVE_DEFAULT entry.
748   // This will overwrite any existing entry for the item.
749   VLOG(2) << this << " -> SYNC UPDATE: REMOVE_DEFAULT: " << sync_item->item_id;
750   sync_item->item_type = sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP;
751   UpdateSyncItemInLocalStorage(profile_, sync_item);
752   SendSyncChange(sync_item, SyncChange::ACTION_UPDATE);
753   return true;
754 }
755 
DeleteSyncItem(const std::string & item_id)756 void AppListSyncableService::DeleteSyncItem(const std::string& item_id) {
757   SyncItem* sync_item = FindSyncItem(item_id);
758   if (!sync_item) {
759     LOG(ERROR) << "DeleteSyncItem: no sync item: " << item_id;
760     return;
761   }
762   if (SyncStarted()) {
763     VLOG(2) << this << " -> SYNC DELETE: " << sync_item->ToString();
764     SyncChange sync_change(FROM_HERE, SyncChange::ACTION_DELETE,
765                            GetSyncDataFromSyncItem(sync_item));
766     sync_processor_->ProcessSyncChanges(FROM_HERE,
767                                         syncer::SyncChangeList(1, sync_change));
768   }
769   RemoveSyncItemFromLocalStorage(profile_, item_id);
770   sync_items_.erase(item_id);
771 }
772 
UpdateSyncItem(const ChromeAppListItem * app_item)773 void AppListSyncableService::UpdateSyncItem(const ChromeAppListItem* app_item) {
774   SyncItem* sync_item = FindSyncItem(app_item->id());
775   if (!sync_item) {
776     LOG(ERROR) << "UpdateItem: no sync item: " << app_item->id();
777     return;
778   }
779   std::string app_item_folder_id = app_item->folder_id();
780   std::string sync_item_orignial_parent_id = sync_item->parent_id;
781   bool changed = UpdateSyncItemFromAppItem(app_item, sync_item);
782   if (!changed) {
783     DVLOG(2) << this << " - Update: SYNC NO CHANGE: " << sync_item->ToString();
784     return;
785   }
786   UpdateSyncItemInLocalStorage(profile_, sync_item);
787   SendSyncChange(sync_item, SyncChange::ACTION_UPDATE);
788 
789   // If the |app_item| is moved out from a user created folder, check if
790   // its original folder becomes a single sync item folder. Clean it up if
791   // it does.
792   if (!sync_item_orignial_parent_id.empty() &&
793       app_item_folder_id != sync_item_orignial_parent_id) {
794     SyncItem* original_folder_item = FindSyncItem(sync_item_orignial_parent_id);
795     RemoveOnlyChildOutOfUserCreatedFolderIfNecessary(original_folder_item);
796   }
797 
798   PruneRedundantPageBreakItems();
799 }
800 
RemoveItem(const std::string & id)801 void AppListSyncableService::RemoveItem(const std::string& id) {
802   RemoveSyncItem(id);
803   model_updater_->RemoveItem(id);
804   PruneEmptySyncFolders();
805   PruneRedundantPageBreakItems();
806 }
807 
RemoveUninstalledItem(const std::string & id)808 void AppListSyncableService::RemoveUninstalledItem(const std::string& id) {
809   RemoveSyncItem(id);
810   model_updater_->RemoveUninstalledItem(id);
811   PruneEmptySyncFolders();
812   PruneRedundantPageBreakItems();
813 }
814 
UpdateItem(const ChromeAppListItem * app_item)815 void AppListSyncableService::UpdateItem(const ChromeAppListItem* app_item) {
816   // Check to see if the item needs to be moved to/from the OEM folder.
817   bool is_oem = AppIsOem(app_item->id());
818   if (!is_oem && app_item->folder_id() == ash::kOemFolderId)
819     model_updater_->MoveItemToFolder(app_item->id(), "");
820   else if (is_oem && app_item->folder_id() != ash::kOemFolderId)
821     model_updater_->MoveItemToFolder(app_item->id(), ash::kOemFolderId);
822 }
823 
RemoveSyncItem(const std::string & id)824 void AppListSyncableService::RemoveSyncItem(const std::string& id) {
825   VLOG(2) << this << ": RemoveSyncItem: " << id.substr(0, 8);
826   auto iter = sync_items_.find(id);
827   if (iter == sync_items_.end()) {
828     DVLOG(2) << this << " : RemoveSyncItem: No Item.";
829     return;
830   }
831 
832   // Check for existing RemoveDefault sync item.
833   SyncItem* sync_item = iter->second.get();
834   sync_pb::AppListSpecifics::AppListItemType type = sync_item->item_type;
835   if (type == sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
836     // RemoveDefault item exists, just return.
837     DVLOG(2) << this << " : RemoveDefault Item exists.";
838     return;
839   }
840 
841   // Check if we're asked to remove a default-installed app.
842   if (InterceptDeleteDefaultApp(sync_item))
843     return;
844 
845   DeleteSyncItem(iter->first);
846 }
847 
ResolveFolderPositions()848 void AppListSyncableService::ResolveFolderPositions() {
849   VLOG(2) << "ResolveFolderPositions.";
850   for (const auto& sync_pair : sync_items_) {
851     SyncItem* sync_item = sync_pair.second.get();
852     if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
853       continue;
854 
855     model_updater_->UpdateAppItemFromSyncItem(
856         sync_item,
857         sync_item->item_id !=
858             ash::kOemFolderId,  // Don't sync oem folder's name.
859         false);                 // Don't sync its folder here.
860   }
861 
862   // Move the OEM folder if one exists and we have not synced its position.
863   if (!FindSyncItem(ash::kOemFolderId)) {
864     model_updater_->ResolveOemFolderPosition(
865         GetPreferredOemFolderPos(),
866         base::BindOnce(
867             [](base::WeakPtr<AppListSyncableService> self,
868                ChromeAppListItem* oem_folder) {
869               if (!self)
870                 return;
871 
872               if (oem_folder) {
873                 VLOG(2) << "Creating new OEM folder sync item: "
874                         << oem_folder->position().ToDebugString();
875                 self->CreateSyncItemFromAppItem(oem_folder);
876               }
877             },
878             weak_ptr_factory_.GetWeakPtr()));
879   }
880 }
881 
PruneEmptySyncFolders()882 void AppListSyncableService::PruneEmptySyncFolders() {
883   std::set<std::string> parent_ids;
884   for (const auto& sync_pair : sync_items_)
885     parent_ids.insert(sync_pair.second->parent_id);
886 
887   for (auto iter = sync_items_.begin(); iter != sync_items_.end();) {
888     SyncItem* sync_item = (iter++)->second.get();
889     if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
890       continue;
891     if (!base::Contains(parent_ids, sync_item->item_id))
892       DeleteSyncItem(sync_item->item_id);
893   }
894 }
895 
896 // AppListSyncableService syncer::SyncableService
897 
InstallDefaultPageBreaksForTest()898 void AppListSyncableService::InstallDefaultPageBreaksForTest() {
899   InstallDefaultPageBreaks();
900 }
901 
WaitUntilReadyToSync(base::OnceClosure done)902 void AppListSyncableService::WaitUntilReadyToSync(base::OnceClosure done) {
903   DCHECK(!wait_until_ready_to_sync_cb_);
904 
905   if (IsInitialized()) {
906     std::move(done).Run();
907   } else {
908     // Wait until initialization is completed in BuildModel();
909     wait_until_ready_to_sync_cb_ = std::move(done);
910   }
911 }
912 
913 base::Optional<syncer::ModelError>
MergeDataAndStartSyncing(syncer::ModelType type,const syncer::SyncDataList & initial_sync_data,std::unique_ptr<syncer::SyncChangeProcessor> sync_processor,std::unique_ptr<syncer::SyncErrorFactory> error_handler)914 AppListSyncableService::MergeDataAndStartSyncing(
915     syncer::ModelType type,
916     const syncer::SyncDataList& initial_sync_data,
917     std::unique_ptr<syncer::SyncChangeProcessor> sync_processor,
918     std::unique_ptr<syncer::SyncErrorFactory> error_handler) {
919   DCHECK(!sync_processor_.get());
920   DCHECK(sync_processor.get());
921   DCHECK(error_handler.get());
922 
923   const bool first_time_user = initial_sync_data.empty();
924   if (first_time_user) {
925     // Post a task to avoid adding the default page break items which can cause
926     // sync changes during sync startup.
927     base::SequencedTaskRunnerHandle::Get()->PostTask(
928         FROM_HERE,
929         base::BindOnce(&AppListSyncableService::InstallDefaultPageBreaks,
930                        weak_ptr_factory_.GetWeakPtr()));
931   }
932 
933   HandleUpdateStarted();
934 
935   // Reset local state and recreate from sync info.
936   DictionaryPrefUpdate pref_update(profile_->GetPrefs(),
937                                    prefs::kAppListLocalState);
938   pref_update->Clear();
939 
940   sync_processor_ = std::move(sync_processor);
941   sync_error_handler_ = std::move(error_handler);
942 
943   VLOG(2) << this << ": MergeDataAndStartSyncing: " << initial_sync_data.size();
944 
945   // Copy all sync items to |unsynced_items|.
946   std::set<std::string> unsynced_items;
947   for (const auto& sync_pair : sync_items_) {
948     unsynced_items.insert(sync_pair.first);
949   }
950 
951   // Create SyncItem entries for initial_sync_data.
952   for (syncer::SyncDataList::const_iterator iter = initial_sync_data.begin();
953        iter != initial_sync_data.end(); ++iter) {
954     const syncer::SyncData& data = *iter;
955     const std::string& item_id = data.GetSpecifics().app_list().item_id();
956     const sync_pb::AppListSpecifics& specifics = data.GetSpecifics().app_list();
957     DVLOG(2) << this << "  Initial Sync Item: " << item_id
958              << " Type: " << specifics.item_type();
959     DCHECK_EQ(syncer::APP_LIST, data.GetDataType());
960     ProcessSyncItemSpecifics(specifics);
961     if (specifics.item_type() != sync_pb::AppListSpecifics::TYPE_FOLDER &&
962         !IsUnRemovableDefaultApp(item_id) && !AppIsOem(item_id) &&
963         !AppIsDefault(profile_, item_id)) {
964       VLOG(2) << "Syncing non-default item: " << item_id;
965       first_app_list_sync_ = false;
966     }
967     unsynced_items.erase(item_id);
968   }
969   // Initial sync data has been processed, it is safe now to add new sync items.
970   initial_sync_data_processed_ = true;
971 
972   // Send unsynced items.
973   syncer::SyncChangeList change_list;
974   for (std::set<std::string>::iterator iter = unsynced_items.begin();
975        iter != unsynced_items.end(); ++iter) {
976     SyncItem* sync_item = FindSyncItem(*iter);
977     // Sync can cause an item to change folders, causing an unsynced folder
978     // item to be removed.
979     if (!sync_item)
980       continue;
981     VLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
982     UpdateSyncItemInLocalStorage(profile_, sync_item);
983     change_list.push_back(SyncChange(FROM_HERE, SyncChange::ACTION_ADD,
984                                      GetSyncDataFromSyncItem(sync_item)));
985   }
986 
987   // Fix items that do not contain valid app list position, required for
988   // builds prior to M53 (crbug.com/677647).
989   for (const auto& sync_pair : sync_items_) {
990     SyncItem* sync_item = sync_pair.second.get();
991     if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_APP ||
992         sync_item->item_ordinal.IsValid()) {
993       continue;
994     }
995     const ChromeAppListItem* app_item =
996         model_updater_->FindItem(sync_item->item_id);
997     if (app_item) {
998       if (UpdateSyncItemFromAppItem(app_item, sync_item)) {
999         VLOG(2) << "Fixing sync item from existing app: " << sync_item;
1000       } else {
1001         sync_item->item_ordinal = syncer::StringOrdinal::CreateInitialOrdinal();
1002         VLOG(2) << "Failed to fix sync item from existing app. "
1003                 << "Generating new position ordinal: " << sync_item;
1004       }
1005     } else {
1006       sync_item->item_ordinal = syncer::StringOrdinal::CreateInitialOrdinal();
1007       VLOG(2) << "Fixing sync item by generating new position ordinal: "
1008               << sync_item;
1009     }
1010     change_list.push_back(SyncChange(FROM_HERE, SyncChange::ACTION_UPDATE,
1011                                      GetSyncDataFromSyncItem(sync_item)));
1012   }
1013 
1014   sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
1015 
1016   HandleUpdateFinished(true /* clean_up_after_init_sync */);
1017 
1018   // Check if already signaled since unit tests make multiple calls.
1019   if (!on_initialized_.is_signaled()) {
1020     on_initialized_.Signal();
1021   }
1022 
1023   return base::nullopt;
1024 }
1025 
StopSyncing(syncer::ModelType type)1026 void AppListSyncableService::StopSyncing(syncer::ModelType type) {
1027   DCHECK_EQ(type, syncer::APP_LIST);
1028 
1029   sync_processor_.reset();
1030   sync_error_handler_.reset();
1031 }
1032 
GetAllSyncDataForTesting() const1033 syncer::SyncDataList AppListSyncableService::GetAllSyncDataForTesting() const {
1034   VLOG(2) << this << ": GetAllSyncData: " << sync_items_.size();
1035   syncer::SyncDataList list;
1036   for (auto iter = sync_items_.begin(); iter != sync_items_.end(); ++iter) {
1037     VLOG(2) << this << " -> SYNC: " << iter->second->ToString();
1038     list.push_back(GetSyncDataFromSyncItem(iter->second.get()));
1039   }
1040   return list;
1041 }
1042 
ProcessSyncChanges(const base::Location & from_here,const syncer::SyncChangeList & change_list)1043 base::Optional<syncer::ModelError> AppListSyncableService::ProcessSyncChanges(
1044     const base::Location& from_here,
1045     const syncer::SyncChangeList& change_list) {
1046   if (!sync_processor_.get()) {
1047     return syncer::ModelError(FROM_HERE,
1048                               "App List syncable service is not started.");
1049   }
1050 
1051   HandleUpdateStarted();
1052 
1053   VLOG(2) << this << ": ProcessSyncChanges: " << change_list.size();
1054   for (syncer::SyncChangeList::const_iterator iter = change_list.begin();
1055        iter != change_list.end(); ++iter) {
1056     const SyncChange& change = *iter;
1057     VLOG(2) << this << "  Change: "
1058             << change.sync_data().GetSpecifics().app_list().item_id() << " ("
1059             << change.change_type() << ")";
1060     if (change.change_type() == SyncChange::ACTION_ADD ||
1061         change.change_type() == SyncChange::ACTION_UPDATE) {
1062       ProcessSyncItemSpecifics(change.sync_data().GetSpecifics().app_list());
1063     } else if (change.change_type() == SyncChange::ACTION_DELETE) {
1064       DeleteSyncItemSpecifics(change.sync_data().GetSpecifics().app_list());
1065     } else {
1066       LOG(ERROR) << "Invalid sync change";
1067     }
1068   }
1069 
1070   HandleUpdateFinished(false /* clean_up_after_init_sync */);
1071 
1072   GetModelUpdater()->NotifyProcessSyncChangesFinished();
1073 
1074   return base::nullopt;
1075 }
1076 
Shutdown()1077 void AppListSyncableService::Shutdown() {
1078   app_service_apps_builder_.reset();
1079 }
1080 
1081 // AppListSyncableService private
1082 
ProcessSyncItemSpecifics(const sync_pb::AppListSpecifics & specifics)1083 bool AppListSyncableService::ProcessSyncItemSpecifics(
1084     const sync_pb::AppListSpecifics& specifics) {
1085   const std::string& item_id = specifics.item_id();
1086   if (item_id.empty()) {
1087     LOG(ERROR) << "AppList item with empty ID";
1088     return false;
1089   }
1090   SyncItem* sync_item = FindSyncItem(item_id);
1091   if (sync_item) {
1092     // If an item of the same type exists, update it.
1093     if (sync_item->item_type == specifics.item_type()) {
1094       UpdateSyncItemFromSync(specifics, sync_item);
1095       ProcessExistingSyncItem(sync_item);
1096       UpdateSyncItemInLocalStorage(profile_, sync_item);
1097       VLOG(2) << this << " <- SYNC UPDATE: " << sync_item->ToString();
1098       return false;
1099     }
1100     // Otherwise, one of the entries should be TYPE_REMOVE_DEFAULT_APP.
1101     if (sync_item->item_type !=
1102             sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP &&
1103         specifics.item_type() !=
1104             sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
1105       LOG(ERROR) << "Synced item type: " << specifics.item_type()
1106                  << " != existing sync item type: " << sync_item->item_type
1107                  << " Deleting item from model!";
1108       model_updater_->RemoveItem(item_id);
1109     }
1110     VLOG(2) << this << " - ProcessSyncItem: Delete existing entry: "
1111             << sync_item->ToString();
1112     sync_items_.erase(item_id);
1113   }
1114 
1115   sync_item = CreateSyncItem(item_id, specifics.item_type());
1116   UpdateSyncItemFromSync(specifics, sync_item);
1117   ProcessNewSyncItem(sync_item);
1118   UpdateSyncItemInLocalStorage(profile_, sync_item);
1119   VLOG(2) << this << " <- SYNC ADD: " << sync_item->ToString();
1120   return true;
1121 }
1122 
ProcessNewSyncItem(SyncItem * sync_item)1123 void AppListSyncableService::ProcessNewSyncItem(SyncItem* sync_item) {
1124   VLOG(2) << "ProcessNewSyncItem: " << sync_item->ToString();
1125   switch (sync_item->item_type) {
1126     case sync_pb::AppListSpecifics::TYPE_APP: {
1127       // New apps are added through ExtensionAppModelBuilder.
1128       // TODO(stevenjb): Determine how to handle app items in sync that
1129       // are not installed (e.g. default / OEM apps).
1130       return;
1131     }
1132     case sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP: {
1133       VLOG(2) << this << ": Uninstall: " << sync_item->ToString();
1134       UninstallExtension(extension_system_->extension_service(),
1135                          extension_registry_, sync_item->item_id);
1136       return;
1137     }
1138     case sync_pb::AppListSpecifics::TYPE_FOLDER: {
1139       // We don't create new folders here, the model will do that.
1140       model_updater_->UpdateAppItemFromSyncItem(
1141           sync_item,
1142           sync_item->item_id !=
1143               ash::kOemFolderId,  // Don't sync oem folder's name.
1144           false);                 // It's a folder itself.
1145       return;
1146     }
1147     case sync_pb::AppListSpecifics::TYPE_OBSOLETE_URL: {
1148       return;
1149     }
1150     case sync_pb::AppListSpecifics::TYPE_PAGE_BREAK: {
1151       // This is can be either a default page break item that was installed by
1152       // default for new users, or a non-default page-break item that was added
1153       // by the user. the ctor of PageBreakAppItem will update the newly-created
1154       // item from its |sync_item|.
1155       model_updater_->AddItem(std::make_unique<PageBreakAppItem>(
1156           profile_, model_updater_.get(), sync_item, sync_item->item_id));
1157       return;
1158     }
1159   }
1160   NOTREACHED() << "Unrecognized sync item type: " << sync_item->ToString();
1161 }
1162 
ProcessExistingSyncItem(SyncItem * sync_item)1163 void AppListSyncableService::ProcessExistingSyncItem(SyncItem* sync_item) {
1164   if (sync_item->item_type ==
1165       sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
1166     return;
1167   }
1168   VLOG(2) << "ProcessExistingSyncItem: " << sync_item->ToString();
1169 
1170   // The only place where sync can change an item's folder. Prevent moving OEM
1171   // item to the folder, other than OEM folder.
1172   const bool update_folder = !AppIsOem(sync_item->item_id);
1173   model_updater_->UpdateAppItemFromSyncItem(
1174       sync_item,
1175       sync_item->item_id != ash::kOemFolderId,  // Don't sync oem folder's name.
1176       update_folder);
1177 }
1178 
SyncStarted()1179 bool AppListSyncableService::SyncStarted() {
1180   if (sync_processor_.get())
1181     return true;
1182   if (flare_.is_null()) {
1183     VLOG(2) << this << ": SyncStarted: Flare.";
1184     flare_ = sync_start_util::GetFlareForSyncableService(profile_->GetPath());
1185     flare_.Run(syncer::APP_LIST);
1186   }
1187   return false;
1188 }
1189 
SendSyncChange(SyncItem * sync_item,SyncChange::SyncChangeType sync_change_type)1190 void AppListSyncableService::SendSyncChange(
1191     SyncItem* sync_item,
1192     SyncChange::SyncChangeType sync_change_type) {
1193   if (!SyncStarted()) {
1194     DVLOG(2) << this << " - SendSyncChange: SYNC NOT STARTED: "
1195              << sync_item->ToString();
1196     return;
1197   }
1198   if (!initial_sync_data_processed_ &&
1199       sync_change_type == SyncChange::ACTION_ADD) {
1200     // This can occur if an initial item is created before its folder item.
1201     // A sync item should already exist for the folder, so we do not want to
1202     // send an ADD event, since that would trigger a CHECK in the sync code.
1203     DCHECK(sync_item->item_type == sync_pb::AppListSpecifics::TYPE_FOLDER);
1204     DVLOG(2) << this << " - SendSyncChange: ADD before initial data processed: "
1205              << sync_item->ToString();
1206     return;
1207   }
1208   if (sync_change_type == SyncChange::ACTION_ADD)
1209     VLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
1210   else
1211     VLOG(2) << this << " -> SYNC UPDATE: " << sync_item->ToString();
1212   SyncChange sync_change(FROM_HERE, sync_change_type,
1213                          GetSyncDataFromSyncItem(sync_item));
1214   sync_processor_->ProcessSyncChanges(FROM_HERE,
1215                                       syncer::SyncChangeList(1, sync_change));
1216 }
1217 
FindSyncItem(const std::string & item_id)1218 AppListSyncableService::SyncItem* AppListSyncableService::FindSyncItem(
1219     const std::string& item_id) {
1220   return const_cast<SyncItem*>(GetSyncItem(item_id));
1221 }
1222 
CreateSyncItem(const std::string & item_id,sync_pb::AppListSpecifics::AppListItemType item_type)1223 AppListSyncableService::SyncItem* AppListSyncableService::CreateSyncItem(
1224     const std::string& item_id,
1225     sync_pb::AppListSpecifics::AppListItemType item_type) {
1226   DCHECK(!base::Contains(sync_items_, item_id));
1227   sync_items_[item_id] = std::make_unique<SyncItem>(item_id, item_type);
1228 
1229   // In case we have pending attributes to apply, process it asynchronously.
1230   if (base::Contains(pending_transfer_map_, item_id)) {
1231     base::SequencedTaskRunnerHandle::Get()->PostTask(
1232         FROM_HERE, base::BindOnce(&AppListSyncableService::ApplyAppAttributes,
1233                                   weak_ptr_factory_.GetWeakPtr(), item_id,
1234                                   std::move(pending_transfer_map_[item_id])));
1235     pending_transfer_map_.erase(item_id);
1236   }
1237 
1238   return sync_items_[item_id].get();
1239 }
1240 
DeleteSyncItemSpecifics(const sync_pb::AppListSpecifics & specifics)1241 void AppListSyncableService::DeleteSyncItemSpecifics(
1242     const sync_pb::AppListSpecifics& specifics) {
1243   const std::string& item_id = specifics.item_id();
1244   if (item_id.empty()) {
1245     LOG(ERROR) << "Delete AppList item with empty ID";
1246     return;
1247   }
1248   VLOG(2) << this << ": DeleteSyncItemSpecifics: " << item_id.substr(0, 8);
1249   auto iter = sync_items_.find(item_id);
1250   if (iter == sync_items_.end())
1251     return;
1252 
1253   // Check if we're asked to remove a default-installed app.
1254   if (InterceptDeleteDefaultApp(iter->second.get()))
1255     return;
1256 
1257   sync_pb::AppListSpecifics::AppListItemType item_type =
1258       iter->second->item_type;
1259   VLOG(2) << this << " <- SYNC DELETE: " << iter->second->ToString();
1260   RemoveSyncItemFromLocalStorage(profile_, item_id);
1261   sync_items_.erase(iter);
1262 
1263   // Only delete apps and page break from the model. Folders will be deleted
1264   // when all children have been deleted.
1265   if (item_type == sync_pb::AppListSpecifics::TYPE_APP ||
1266       item_type == sync_pb::AppListSpecifics::TYPE_PAGE_BREAK) {
1267     model_updater_->RemoveItem(item_id);
1268   }
1269 }
1270 
GetPreferredOemFolderPos()1271 syncer::StringOrdinal AppListSyncableService::GetPreferredOemFolderPos() {
1272   VLOG(2) << "GetPreferredOemFolderPos: " << first_app_list_sync_;
1273   if (!first_app_list_sync_) {
1274     VLOG(2) << "Sync items exist, placing OEM folder at end.";
1275     syncer::StringOrdinal last;
1276     for (const auto& sync_pair : sync_items_) {
1277       SyncItem* sync_item = sync_pair.second.get();
1278       if (sync_item->item_ordinal.IsValid() &&
1279           (!last.IsValid() || sync_item->item_ordinal.GreaterThan(last))) {
1280         last = sync_item->item_ordinal;
1281       }
1282     }
1283     if (last.IsValid())
1284       return last.CreateAfter();
1285   }
1286   return syncer::StringOrdinal();
1287 }
1288 
AppIsOem(const std::string & id)1289 bool AppListSyncableService::AppIsOem(const std::string& id) {
1290   const ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile_);
1291   if (arc_prefs && arc_prefs->IsOem(id))
1292     return true;
1293 
1294   if (!extension_system_->extension_service())
1295     return false;
1296   const extensions::Extension* extension =
1297       extension_registry_->GetExtensionById(
1298           id, extensions::ExtensionRegistry::EVERYTHING);
1299   return extension && extension->was_installed_by_oem();
1300 }
1301 
ToString() const1302 std::string AppListSyncableService::SyncItem::ToString() const {
1303   std::string res = item_id.substr(0, 8);
1304   if (item_type == sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
1305     res += " { RemoveDefault }";
1306   } else if (item_type == sync_pb::AppListSpecifics::TYPE_PAGE_BREAK) {
1307     res += " { PageBreakItem }";
1308     res += " [" + item_ordinal.ToDebugString() + "]";
1309   } else {
1310     res += " { " + item_name + " }";
1311     res += " [" + item_ordinal.ToDebugString() + "]";
1312     if (!parent_id.empty())
1313       res += " <" + parent_id.substr(0, 8) + ">";
1314     res += " [" + item_pin_ordinal.ToDebugString() + "]";
1315   }
1316   return res;
1317 }
1318 
1319 std::vector<AppListSyncableService::SyncItem*>
GetSortedTopLevelSyncItems() const1320 AppListSyncableService::GetSortedTopLevelSyncItems() const {
1321   // Filter out items in folder.
1322   std::vector<SyncItem*> sync_items;
1323   for (const auto& sync_pair : sync_items_) {
1324     const auto* sync_item = sync_pair.second.get();
1325     if (IsTopLevelAppItem(*sync_item) && sync_item->item_ordinal.IsValid())
1326       sync_items.emplace_back(sync_pair.second.get());
1327   }
1328 
1329   // Sort remaining items based on their positions.
1330   std::sort(sync_items.begin(), sync_items.end(),
1331             [](SyncItem* const& item1, SyncItem* const& item2) -> bool {
1332               return item1->item_ordinal.LessThan(item2->item_ordinal);
1333             });
1334   return sync_items;
1335 }
1336 
PruneRedundantPageBreakItems()1337 void AppListSyncableService::PruneRedundantPageBreakItems() {
1338   auto top_level_sync_items = GetSortedTopLevelSyncItems();
1339 
1340   // If the first item is a "page break" item, delete it. If there are
1341   // contiguous "page break" items, delete duplicate.
1342   bool was_page_break = true;
1343   for (auto iter = top_level_sync_items.begin();
1344        iter != top_level_sync_items.end();) {
1345     if (!IsPageBreakItem(**iter)) {
1346       was_page_break = false;
1347       ++iter;
1348       continue;
1349     }
1350     auto current_iter = iter++;
1351     if (was_page_break) {
1352       DeleteSyncItem((*current_iter)->item_id);
1353       iter = top_level_sync_items.erase(current_iter);
1354     } else {
1355       was_page_break = true;
1356     }
1357   }
1358 
1359   // Remove the trailing "page break" item if it exists.
1360   if (!top_level_sync_items.empty() &&
1361       IsPageBreakItem(*top_level_sync_items.back())) {
1362     DeleteSyncItem(top_level_sync_items.back()->item_id);
1363   }
1364 
1365   // Remove all the "page break" items that are in folder. No such item should
1366   // exist in folder. It should be safe to remove them if it do occur.
1367   for (auto iter = sync_items_.begin(); iter != sync_items_.end();) {
1368     const auto* sync_item = (iter++)->second.get();
1369     if (IsTopLevelAppItem(*sync_item) || !IsPageBreakItem(*sync_item))
1370       continue;
1371 
1372     LOG(ERROR) << "Delete a page break item in folder: " << sync_item->item_id;
1373     DeleteSyncItem(sync_item->item_id);
1374   }
1375 }
1376 
InstallDefaultPageBreaks()1377 void AppListSyncableService::InstallDefaultPageBreaks() {
1378   for (size_t i = 0; i < kDefaultPageBreakAppIdsLength; ++i) {
1379     auto* const id = kDefaultPageBreakAppIds[i];
1380     auto* sync_item = GetSyncItem(id);
1381     if (sync_item) {
1382       // The user may have cleared their sync from
1383       // https://chrome.google.com/sync, so it may appear here that it's a new
1384       // user, while in fact on this device, it's not. We don't want to recreate
1385       // and re-add an already existing default page break item.
1386       continue;
1387     }
1388 
1389     auto page_break_item = std::make_unique<PageBreakAppItem>(
1390         profile(), model_updater_.get(), nullptr /* sync_item */, id);
1391     page_break_item->SetName("__default_page_break__");
1392     AddItem(std::move(page_break_item));
1393   }
1394 }
1395 
UpdateSyncItemFromSync(const sync_pb::AppListSpecifics & specifics,AppListSyncableService::SyncItem * item)1396 void AppListSyncableService::UpdateSyncItemFromSync(
1397     const sync_pb::AppListSpecifics& specifics,
1398     AppListSyncableService::SyncItem* item) {
1399   DCHECK_EQ(item->item_id, specifics.item_id());
1400   item->item_type = specifics.item_type();
1401   item->item_name = specifics.item_name();
1402 
1403   // Ignore update to put item into the OEM folder in case app is not OEM. This
1404   // can happen when app is installed on several devices where app is OEM on one
1405   // device and not on another devices.
1406   if (specifics.parent_id() != ash::kOemFolderId || AppIsOem(item->item_id))
1407     item->parent_id = specifics.parent_id();
1408   if (specifics.has_item_ordinal())
1409     item->item_ordinal = syncer::StringOrdinal(specifics.item_ordinal());
1410   if (specifics.has_item_pin_ordinal()) {
1411     item->item_pin_ordinal =
1412         syncer::StringOrdinal(specifics.item_pin_ordinal());
1413   }
1414 }
1415 
UpdateSyncItemFromAppItem(const ChromeAppListItem * app_item,AppListSyncableService::SyncItem * sync_item)1416 bool AppListSyncableService::UpdateSyncItemFromAppItem(
1417     const ChromeAppListItem* app_item,
1418     AppListSyncableService::SyncItem* sync_item) {
1419   DCHECK_EQ(sync_item->item_id, app_item->id());
1420 
1421   // Page breaker should not be added in a folder.
1422   DCHECK(!app_item->is_page_break() || app_item->folder_id().empty());
1423 
1424   bool changed = false;
1425   // Allow sync changes for parent only for non OEM app.
1426   if (sync_item->parent_id != app_item->folder_id() &&
1427       !AppIsOem(app_item->id())) {
1428     sync_item->parent_id = app_item->folder_id();
1429     changed = true;
1430   }
1431   if (sync_item->item_name != app_item->name()) {
1432     sync_item->item_name = app_item->name();
1433     changed = true;
1434   }
1435   if (!sync_item->item_ordinal.IsValid() ||
1436       !app_item->position().Equals(sync_item->item_ordinal)) {
1437     sync_item->item_ordinal = app_item->position();
1438     changed = true;
1439   }
1440   return changed;
1441 }
1442 
1443 }  // namespace app_list
1444