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 #ifndef CHROME_BROWSER_UI_APP_LIST_APP_LIST_SYNCABLE_SERVICE_H_
6 #define CHROME_BROWSER_UI_APP_LIST_APP_LIST_SYNCABLE_SERVICE_H_
7 
8 #include <stddef.h>
9 
10 #include <map>
11 #include <memory>
12 #include <string>
13 #include <vector>
14 
15 #include "base/callback.h"
16 #include "base/memory/weak_ptr.h"
17 #include "base/observer_list.h"
18 #include "base/one_shot_event.h"
19 #include "build/build_config.h"
20 #include "chrome/browser/sync/glue/sync_start_util.h"
21 #include "components/keyed_service/core/keyed_service.h"
22 #include "components/sync/model/string_ordinal.h"
23 #include "components/sync/model/sync_change.h"
24 #include "components/sync/model/sync_change_processor.h"
25 #include "components/sync/model/sync_error_factory.h"
26 #include "components/sync/model/syncable_service.h"
27 #include "components/sync/protocol/app_list_specifics.pb.h"
28 
29 class AppListModelUpdater;
30 class AppServiceAppModelBuilder;
31 class ChromeAppListItem;
32 class Profile;
33 
34 namespace extensions {
35 class ExtensionRegistry;
36 class ExtensionSystem;
37 }
38 
39 namespace sync_pb {
40 class AppListSpecifics;
41 }
42 
43 namespace user_prefs {
44 class PrefRegistrySyncable;
45 }
46 
47 namespace app_list {
48 
49 // Keyed Service that owns, stores, and syncs an AppListModel for a profile.
50 class AppListSyncableService : public syncer::SyncableService,
51                                public KeyedService {
52  public:
53   struct SyncItem {
54     SyncItem(const std::string& id,
55              sync_pb::AppListSpecifics::AppListItemType type);
56     ~SyncItem();
57     const std::string item_id;
58     sync_pb::AppListSpecifics::AppListItemType item_type;
59     std::string item_name;
60     std::string parent_id;
61     syncer::StringOrdinal item_ordinal;
62     syncer::StringOrdinal item_pin_ordinal;
63 
64     std::string ToString() const;
65   };
66 
67   class Observer {
68    public:
69     // Notifies that sync model was updated.
70     virtual void OnSyncModelUpdated() = 0;
71 
72    protected:
73     virtual ~Observer() = default;
74   };
75 
76   // An app list model updater factory function used by tests.
77   using ModelUpdaterFactoryCallback =
78       base::Callback<std::unique_ptr<AppListModelUpdater>()>;
79 
80   // Sets and resets an app list model updater factory function for tests.
81   class ScopedModelUpdaterFactoryForTest {
82    public:
83     explicit ScopedModelUpdaterFactoryForTest(
84         const ModelUpdaterFactoryCallback& factory);
85     ScopedModelUpdaterFactoryForTest(const ScopedModelUpdaterFactoryForTest&) =
86         delete;
87     ScopedModelUpdaterFactoryForTest& operator=(
88         const ScopedModelUpdaterFactoryForTest&) = delete;
89     ~ScopedModelUpdaterFactoryForTest();
90 
91    private:
92     ModelUpdaterFactoryCallback factory_;
93   };
94 
95   using SyncItemMap = std::map<std::string, std::unique_ptr<SyncItem>>;
96 
97   // Populates the model when |profile|'s extension system is ready.
98   explicit AppListSyncableService(Profile* profile);
99   AppListSyncableService(const AppListSyncableService&) = delete;
100   AppListSyncableService& operator=(const AppListSyncableService&) = delete;
101   ~AppListSyncableService() override;
102 
103   // Registers prefs to support local storage.
104   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
105 
106   // Some sync behavior depends on whether or not an app was installed by
107   // default (as opposed to e.g. installed via explicit user action). Some
108   // tests want the AppListSyncableService to consider an app to be installed
109   // by default, without going through the heavyweight process of completely
110   // installing an app. These functions facilitate that.
111   static bool AppIsDefaultForTest(Profile* profile, const std::string& id);
112   static void SetAppIsDefaultForTest(Profile* profile, const std::string& id);
113 
114   // Adds |item| to |sync_items_| and |model_|. If a sync item already exists,
115   // updates the existing sync item instead.
116   void AddItem(std::unique_ptr<ChromeAppListItem> app_item);
117 
118   // Removes sync item matching |id|.
119   void RemoveItem(const std::string& id);
120 
121   // Removes sync item matching |id| after item uninstall.
122   void RemoveUninstalledItem(const std::string& id);
123 
124   // Called when properties of an item may have changed, e.g. default/oem state.
125   void UpdateItem(const ChromeAppListItem* app_item);
126 
127   // Returns the existing sync item matching |id| or NULL.
128   const SyncItem* GetSyncItem(const std::string& id) const;
129 
130   // Transfers app attributes, such as parent folder id, position in App
131   // Launcher and pin position on the shelf from one app to another app. Target
132   // app defined by |to_app_id| is not required to be present at call time. In
133   // which case attributes would be applied once the target app appears on the
134   // device. Note, pending attributes are not preserved between the user
135   // sessions. This functionality is primarily used for migrating app in case
136   // app id is changed but it is required to preserve position in App Launcher
137   // and in shelf.
138   // Returns true on success and false in case app defined by |from_app_id|
139   // does not exist.
140   bool TransferItemAttributes(const std::string& from_app_id,
141                               const std::string& to_app_id);
142 
143   // Sets the name of the folder for OEM apps.
144   void SetOemFolderName(const std::string& name);
145 
146   // Returns optional pin position for the app specified by |app_id|. If app is
147   // not synced or does not have associated pin position then empty ordinal is
148   // returned.
149   syncer::StringOrdinal GetPinPosition(const std::string& app_id);
150 
151   // Sets pin position and how it is pinned for the app specified by |app_id|.
152   // Empty |item_pin_ordinal| indicates that the app has no pin.
153   void SetPinPosition(const std::string& app_id,
154                       const syncer::StringOrdinal& item_pin_ordinal);
155 
156   // Gets the app list model updater.
157   AppListModelUpdater* GetModelUpdater();
158 
159   // Returns true if this service was initialized.
160   bool IsInitialized() const;
161 
162   // Signalled when AppListSyncableService is Initialized.
on_initialized()163   const base::OneShotEvent& on_initialized() const { return on_initialized_; }
164 
165   // Returns true if sync was started.
166   bool IsSyncing() const;
167 
168   // Registers new observers and makes sure that service is started.
169   void AddObserverAndStart(Observer* observer);
170   void RemoveObserver(Observer* observer);
171 
profile()172   Profile* profile() { return profile_; }
173   size_t GetNumSyncItemsForTest();
GetOemFolderNameForTest()174   const std::string& GetOemFolderNameForTest() const {
175     return oem_folder_name_;
176   }
177 
178   void InstallDefaultPageBreaksForTest();
179 
sync_items()180   const SyncItemMap& sync_items() const { return sync_items_; }
181 
182   // syncer::SyncableService
183   void WaitUntilReadyToSync(base::OnceClosure done) override;
184   base::Optional<syncer::ModelError> MergeDataAndStartSyncing(
185       syncer::ModelType type,
186       const syncer::SyncDataList& initial_sync_data,
187       std::unique_ptr<syncer::SyncChangeProcessor> sync_processor,
188       std::unique_ptr<syncer::SyncErrorFactory> error_handler) override;
189   void StopSyncing(syncer::ModelType type) override;
190   syncer::SyncDataList GetAllSyncDataForTesting() const;
191   base::Optional<syncer::ModelError> ProcessSyncChanges(
192       const base::Location& from_here,
193       const syncer::SyncChangeList& change_list) override;
194 
195   // KeyedService
196   void Shutdown() override;
197 
198  private:
199   class ModelUpdaterObserver;
200 
201   // Builds the model once ExtensionService is ready.
202   void BuildModel();
203 
204   // Returns true if sync has restarted, otherwise runs |flare_|.
205   bool SyncStarted();
206 
207   // If |app_item| matches an existing sync item, returns it. Otherwise adds
208   // |app_item| to |sync_items_| and returns the new item. If |app_item| is
209   // invalid returns NULL.
210   SyncItem* FindOrAddSyncItem(const ChromeAppListItem* app_item);
211 
212   // Creates a sync item for |app_item| and sends an ADD SyncChange event.
213   SyncItem* CreateSyncItemFromAppItem(const ChromeAppListItem* app_item);
214 
215   // If a sync item for |app_item| already exists, update |app_item| from the
216   // sync item, otherwise create a new sync item from |app_item|.
217   void AddOrUpdateFromSyncItem(const ChromeAppListItem* app_item);
218 
219   // Either uninstalling a default app or remove the REMOVE_DEFAULT sync item.
220   // Returns true if the app is removed. Otherwise deletes the existing sync
221   // item and returns false.
222   bool RemoveDefaultApp(const ChromeAppListItem* item, SyncItem* sync_item);
223 
224   // Returns whether the delete-sync-item request was for a default app. If
225   // true, the |sync_item| is set to REMOVE_DEFAULT and bounced back to the
226   // sync server. The caller should abort deleting the |sync_item|.
227   bool InterceptDeleteDefaultApp(SyncItem* sync_item);
228 
229   // Deletes a sync item from |sync_items_| and sends a DELETE action.
230   void DeleteSyncItem(const std::string& item_id);
231 
232   // Updates existing entry in |sync_items_| from |app_item|.
233   void UpdateSyncItem(const ChromeAppListItem* app_item);
234 
235   // Removes sync item matching |id|.
236   void RemoveSyncItem(const std::string& id);
237 
238   // Updates folder items that may get created during initial sync.
239   void ResolveFolderPositions();
240 
241   // Removes any empty SyncItem folders and deletes them from sync. Called
242   // after a sync item is removed (which may result in an empty folder).
243   void PruneEmptySyncFolders();
244 
245   // Creates or updates a SyncItem from |specifics|. Returns true if a new item
246   // was created.
247   // TODO(crbug.com/1057577): Change return type to void.
248   bool ProcessSyncItemSpecifics(const sync_pb::AppListSpecifics& specifics);
249 
250   // Handles a newly created sync item (e.g. creates a new AppItem and adds it
251   // to the model or uninstalls a deleted default item.
252   void ProcessNewSyncItem(SyncItem* sync_item);
253 
254   // Handles an existing sync item.
255   void ProcessExistingSyncItem(SyncItem* sync_item);
256 
257   // Sends ADD or CHANGED for sync item.
258   void SendSyncChange(SyncItem* sync_item,
259                       syncer::SyncChange::SyncChangeType sync_change_type);
260 
261   // Returns an existing SyncItem corresponding to |item_id| or NULL.
262   SyncItem* FindSyncItem(const std::string& item_id);
263 
264   // Creates a new sync item for |item_id|.
265   SyncItem* CreateSyncItem(
266       const std::string& item_id,
267       sync_pb::AppListSpecifics::AppListItemType item_type);
268 
269   // Deletes a SyncItem matching |specifics|.
270   void DeleteSyncItemSpecifics(const sync_pb::AppListSpecifics& specifics);
271 
272   // Gets the preferred location for the OEM folder. It may return an invalid
273   // position and the final OEM folder position will be determined in the
274   // AppListModel.
275   syncer::StringOrdinal GetPreferredOemFolderPos();
276 
277   // Returns true if an extension matching |id| exists and was installed by
278   // an OEM (extension->was_installed_by_oem() is true).
279   bool AppIsOem(const std::string& id);
280 
281   // Initializes sync items from the local storage while sync service is not
282   // enabled.
283   void InitFromLocalStorage();
284 
285   // Helper that notifies observers that sync model has been updated.
286   void NotifyObserversSyncUpdated();
287 
288   // Handles model update start/finish.
289   void HandleUpdateStarted();
290   void HandleUpdateFinished(bool clean_up_after_init_sync);
291 
292   // Cleans up the folder sync item with only one item in it.
293   // There are some edge cases with synch which will create a folder with only
294   // one item in it, which is not legitimate and the folder should be removed.
295   // We will find such folders after the initial sync and clean them up.
296   void CleanUpSingleItemSyncFolder();
297 
298   // Returns child item if |sync_item| is a user created folder with only one
299   // child item in it; otherwise, returns nullptr.
300   SyncItem* GetOnlyChildOfUserCreatedFolder(SyncItem* sync_item);
301 
302   // Returns true if |sync_item| is a user created folder with only one
303   // child item in it, the child item will be removed out of the folder and
304   // place at the same location of its original folder.
305   // Otherwise, return false, no change will be made.
306   bool RemoveOnlyChildOutOfUserCreatedFolderIfNecessary(SyncItem* sync_item);
307 
308   // Returns true if extension service is ready.
309   bool IsExtensionServiceReady() const;
310 
311   // Returns a list of top level sync items sorted by item ordinal.
312   std::vector<SyncItem*> GetSortedTopLevelSyncItems() const;
313 
314   // Remove leading, trailing and duplicate "page break" items in sorted top
315   // level item list.
316   void PruneRedundantPageBreakItems();
317 
318   // Installs the default page break items. This is only called for first time
319   // users.
320   void InstallDefaultPageBreaks();
321 
322   // Applies sync changes to the local item.
323   void UpdateSyncItemFromSync(const sync_pb::AppListSpecifics& specifics,
324                               AppListSyncableService::SyncItem* item);
325 
326   // Applies changes from the local item to sync item.
327   bool UpdateSyncItemFromAppItem(const ChromeAppListItem* app_item,
328                                  AppListSyncableService::SyncItem* sync_item);
329 
330   // Sets position, folder id and pin position for the app |app_id|. Attributes
331   // are taken from the sync item |attributes|. This generates sync update and
332   // notifies app models and Chrome shelf controller that are automatically
333   // refreshed.
334   void ApplyAppAttributes(const std::string& app_id,
335                           std::unique_ptr<SyncItem> attributes);
336 
337   Profile* profile_;
338   extensions::ExtensionSystem* extension_system_;
339   extensions::ExtensionRegistry* extension_registry_;
340   std::unique_ptr<AppListModelUpdater> model_updater_;
341   std::unique_ptr<ModelUpdaterObserver> model_updater_observer_;
342 
343   std::unique_ptr<AppServiceAppModelBuilder> app_service_apps_builder_;
344   std::unique_ptr<syncer::SyncChangeProcessor> sync_processor_;
345   std::unique_ptr<syncer::SyncErrorFactory> sync_error_handler_;
346   SyncItemMap sync_items_;
347   // Map that keeps pending request to transfer attributes from one app to
348   // another.
349   SyncItemMap pending_transfer_map_;
350   syncer::SyncableService::StartSyncFlare flare_;
351   bool initial_sync_data_processed_ = false;
352   bool first_app_list_sync_ = true;
353   std::string oem_folder_name_;
354   // Callback to install default page breaks.
355   // Only set for first time user for tablet form devices.
356   base::OnceClosure install_default_page_breaks_;
357   base::OnceClosure wait_until_ready_to_sync_cb_;
358 
359   // List of observers.
360   base::ObserverList<Observer>::Unchecked observer_list_;
361   base::OneShotEvent on_initialized_;
362 
363   base::WeakPtrFactory<AppListSyncableService> weak_ptr_factory_{this};
364 };
365 
366 }  // namespace app_list
367 
368 #endif  // CHROME_BROWSER_UI_APP_LIST_APP_LIST_SYNCABLE_SERVICE_H_
369