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