1 // Copyright 2020 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/ash/holding_space/holding_space_keyed_service.h"
6 
7 #include "ash/public/cpp/holding_space/holding_space_controller.h"
8 #include "ash/public/cpp/holding_space/holding_space_image.h"
9 #include "ash/public/cpp/holding_space/holding_space_item.h"
10 #include "ash/public/cpp/holding_space/holding_space_metrics.h"
11 #include "ash/public/cpp/holding_space/holding_space_prefs.h"
12 #include "base/files/file_path.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/chromeos/file_manager/app_id.h"
15 #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.h"
18 #include "chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.h"
19 #include "chrome/browser/ui/ash/holding_space/holding_space_persistence_delegate.h"
20 #include "chrome/browser/ui/ash/holding_space/holding_space_util.h"
21 #include "components/account_id/account_id.h"
22 #include "components/prefs/scoped_user_pref_update.h"
23 #include "storage/browser/file_system/file_system_url.h"
24 
25 namespace ash {
26 
27 namespace {
28 
29 // Helpers ---------------------------------------------------------------------
30 
31 // TODO(crbug.com/1131266): Track alternative type in `HoldingSpaceItem`.
32 // Returns a holding space item other than the one provided which is backed by
33 // the same file path in the specified `model`.
GetAlternativeHoldingSpaceItem(const HoldingSpaceModel & model,const HoldingSpaceItem * item)34 base::Optional<const HoldingSpaceItem*> GetAlternativeHoldingSpaceItem(
35     const HoldingSpaceModel& model,
36     const HoldingSpaceItem* item) {
37   for (const auto& candidate_item : model.items()) {
38     if (candidate_item.get() == item)
39       continue;
40     if (candidate_item->file_path() == item->file_path())
41       return candidate_item.get();
42   }
43   return base::nullopt;
44 }
45 
46 // Returns the singleton profile manager for the browser process.
GetProfileManager()47 ProfileManager* GetProfileManager() {
48   return g_browser_process->profile_manager();
49 }
50 
51 // Records the time from the first availability of the holding space feature
52 // to the time of the first item being added into holding space.
RecordTimeFromFirstAvailabilityToFirstAdd(Profile * profile)53 void RecordTimeFromFirstAvailabilityToFirstAdd(Profile* profile) {
54   base::Time time_of_first_availability =
55       holding_space_prefs::GetTimeOfFirstAvailability(profile->GetPrefs())
56           .value();
57   base::Time time_of_first_add =
58       holding_space_prefs::GetTimeOfFirstAdd(profile->GetPrefs()).value();
59   holding_space_metrics::RecordTimeFromFirstAvailabilityToFirstAdd(
60       time_of_first_add - time_of_first_availability);
61 }
62 
63 // Records the time from the first entry to the first pin into holding space.
64 // Note that this time may be zero if the user pinned their first file before
65 // having ever entered holding space.
RecordTimeFromFirstEntryToFirstPin(Profile * profile)66 void RecordTimeFromFirstEntryToFirstPin(Profile* profile) {
67   base::Time time_of_first_pin =
68       holding_space_prefs::GetTimeOfFirstPin(profile->GetPrefs()).value();
69   base::Time time_of_first_entry =
70       holding_space_prefs::GetTimeOfFirstEntry(profile->GetPrefs())
71           .value_or(time_of_first_pin);
72   holding_space_metrics::RecordTimeFromFirstEntryToFirstPin(
73       time_of_first_pin - time_of_first_entry);
74 }
75 
76 }  // namespace
77 
78 // HoldingSpaceKeyedService ----------------------------------------------------
79 
HoldingSpaceKeyedService(Profile * profile,const AccountId & account_id)80 HoldingSpaceKeyedService::HoldingSpaceKeyedService(Profile* profile,
81                                                    const AccountId& account_id)
82     : profile_(profile),
83       account_id_(account_id),
84       holding_space_client_(profile),
85       thumbnail_loader_(profile) {
86   // Mark when the holding space feature first became available. If this is not
87   // the first time that holding space became available, this will no-op.
88   holding_space_prefs::MarkTimeOfFirstAvailability(profile_->GetPrefs());
89 
90   // The associated profile may not be ready yet. If it is, we can immediately
91   // proceed with profile dependent initialization.
92   ProfileManager* const profile_manager = GetProfileManager();
93   if (profile_manager->IsValidProfile(profile)) {
94     OnProfileReady();
95     return;
96   }
97 
98   // Otherwise we need to wait for the profile to be added.
99   profile_manager_observer_.Add(profile_manager);
100 }
101 
~HoldingSpaceKeyedService()102 HoldingSpaceKeyedService::~HoldingSpaceKeyedService() {
103   if (HoldingSpaceController::Get()) {
104     // For BrowserWithTestWindowTest that releases profile and its keyed
105     // services before ash Shell.
106     HoldingSpaceController::Get()->RegisterClientAndModelForUser(
107         account_id_, /*client=*/nullptr, /*model=*/nullptr);
108   }
109 }
110 
111 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)112 void HoldingSpaceKeyedService::RegisterProfilePrefs(
113     user_prefs::PrefRegistrySyncable* registry) {
114   // TODO(crbug.com/1131266): Move to `ash::holding_space_prefs`.
115   HoldingSpacePersistenceDelegate::RegisterProfilePrefs(registry);
116 }
117 
AddPinnedFile(const storage::FileSystemURL & file_system_url)118 void HoldingSpaceKeyedService::AddPinnedFile(
119     const storage::FileSystemURL& file_system_url) {
120   if (holding_space_model_.GetItem(HoldingSpaceItem::GetFileBackedItemId(
121           HoldingSpaceItem::Type::kPinnedFile, file_system_url.path()))) {
122     return;
123   }
124 
125   // Mark when the first pin to holding space occurred. If this is not the first
126   // pin to holding space, this will no-op. If this is the first pin, record the
127   // amount of time from first entry to first pin into holding space.
128   if (holding_space_prefs::MarkTimeOfFirstPin(profile_->GetPrefs()))
129     RecordTimeFromFirstEntryToFirstPin(profile_);
130 
131   std::unique_ptr<HoldingSpaceItem> holding_space_item =
132       HoldingSpaceItem::CreateFileBackedItem(
133           HoldingSpaceItem::Type::kPinnedFile, file_system_url.path(),
134           file_system_url.ToGURL(),
135           holding_space_util::ResolveImage(&thumbnail_loader_,
136                                            HoldingSpaceItem::Type::kPinnedFile,
137                                            file_system_url.path()));
138 
139   // When pinning an item which already exists in holding space, the pin action
140   // should be recorded on the alternative item backed by the same file path if
141   // such an item exists. Otherwise the only type of holding space item pinned
142   // will be thought to be `kPinnedFile`.
143   const HoldingSpaceItem* holding_space_item_to_record =
144       GetAlternativeHoldingSpaceItem(holding_space_model_,
145                                      holding_space_item.get())
146           .value_or(holding_space_item.get());
147 
148   holding_space_metrics::RecordItemAction(
149       {holding_space_item_to_record}, holding_space_metrics::ItemAction::kPin);
150 
151   AddItem(std::move(holding_space_item));
152 }
153 
RemovePinnedFile(const storage::FileSystemURL & file_system_url)154 void HoldingSpaceKeyedService::RemovePinnedFile(
155     const storage::FileSystemURL& file_system_url) {
156   const HoldingSpaceItem* holding_space_item =
157       holding_space_model_.GetItem(HoldingSpaceItem::GetFileBackedItemId(
158           HoldingSpaceItem::Type::kPinnedFile, file_system_url.path()));
159   if (!holding_space_item)
160     return;
161 
162   // When removing a pinned item, the unpin action should be recorded on the
163   // alternative item backed by the same file path if such an item exists. This
164   // will give more insight as to what types of items are being unpinned than
165   // would otherwise be known if only `kPinnedFile` was recorded.
166   const HoldingSpaceItem* holding_space_item_to_record =
167       GetAlternativeHoldingSpaceItem(holding_space_model_, holding_space_item)
168           .value_or(holding_space_item);
169 
170   holding_space_metrics::RecordItemAction(
171       {holding_space_item_to_record},
172       holding_space_metrics::ItemAction::kUnpin);
173 
174   holding_space_model_.RemoveItem(holding_space_item->id());
175 }
176 
ContainsPinnedFile(const storage::FileSystemURL & file_system_url) const177 bool HoldingSpaceKeyedService::ContainsPinnedFile(
178     const storage::FileSystemURL& file_system_url) const {
179   return holding_space_model_.GetItem(HoldingSpaceItem::GetFileBackedItemId(
180       HoldingSpaceItem::Type::kPinnedFile, file_system_url.path()));
181 }
182 
GetPinnedFiles() const183 std::vector<GURL> HoldingSpaceKeyedService::GetPinnedFiles() const {
184   std::vector<GURL> pinned_files;
185   for (const auto& item : holding_space_model_.items()) {
186     if (item->type() == HoldingSpaceItem::Type::kPinnedFile)
187       pinned_files.push_back(item->file_system_url());
188   }
189   return pinned_files;
190 }
191 
AddScreenshot(const base::FilePath & screenshot_file)192 void HoldingSpaceKeyedService::AddScreenshot(
193     const base::FilePath& screenshot_file) {
194   GURL file_system_url =
195       holding_space_util::ResolveFileSystemUrl(profile_, screenshot_file);
196   if (file_system_url.is_empty())
197     return;
198 
199   AddItem(HoldingSpaceItem::CreateFileBackedItem(
200       HoldingSpaceItem::Type::kScreenshot, screenshot_file, file_system_url,
201       holding_space_util::ResolveImage(&thumbnail_loader_,
202                                        HoldingSpaceItem::Type::kScreenshot,
203                                        screenshot_file)));
204 }
205 
AddDownload(const base::FilePath & download_file)206 void HoldingSpaceKeyedService::AddDownload(
207     const base::FilePath& download_file) {
208   const bool already_exists =
209       holding_space_model_.GetItem(HoldingSpaceItem::GetFileBackedItemId(
210           HoldingSpaceItem::Type::kDownload, download_file));
211   if (already_exists)
212     return;
213 
214   GURL file_system_url =
215       holding_space_util::ResolveFileSystemUrl(profile_, download_file);
216   if (file_system_url.is_empty())
217     return;
218 
219   AddItem(HoldingSpaceItem::CreateFileBackedItem(
220       HoldingSpaceItem::Type::kDownload, download_file, file_system_url,
221       holding_space_util::ResolveImage(&thumbnail_loader_,
222                                        HoldingSpaceItem::Type::kDownload,
223                                        download_file)));
224 }
225 
AddNearbyShare(const base::FilePath & nearby_share_path)226 void HoldingSpaceKeyedService::AddNearbyShare(
227     const base::FilePath& nearby_share_path) {
228   const bool already_exists =
229       holding_space_model_.GetItem(HoldingSpaceItem::GetFileBackedItemId(
230           HoldingSpaceItem::Type::kNearbyShare, nearby_share_path));
231   if (already_exists)
232     return;
233 
234   GURL file_system_url =
235       holding_space_util::ResolveFileSystemUrl(profile_, nearby_share_path);
236   if (file_system_url.is_empty())
237     return;
238 
239   AddItem(HoldingSpaceItem::CreateFileBackedItem(
240       HoldingSpaceItem::Type::kNearbyShare, nearby_share_path, file_system_url,
241       holding_space_util::ResolveImage(&thumbnail_loader_,
242                                        HoldingSpaceItem::Type::kNearbyShare,
243                                        nearby_share_path)));
244 }
245 
AddScreenRecording(const base::FilePath & screen_recording_file)246 void HoldingSpaceKeyedService::AddScreenRecording(
247     const base::FilePath& screen_recording_file) {
248   GURL file_system_url =
249       holding_space_util::ResolveFileSystemUrl(profile_, screen_recording_file);
250   if (file_system_url.is_empty())
251     return;
252 
253   AddItem(HoldingSpaceItem::CreateFileBackedItem(
254       HoldingSpaceItem::Type::kScreenRecording, screen_recording_file,
255       file_system_url,
256       holding_space_util::ResolveImage(&thumbnail_loader_,
257                                        HoldingSpaceItem::Type::kScreenRecording,
258                                        screen_recording_file)));
259 }
260 
AddItem(std::unique_ptr<HoldingSpaceItem> item)261 void HoldingSpaceKeyedService::AddItem(std::unique_ptr<HoldingSpaceItem> item) {
262   // Mark the time when the user's first item was added to holding space. Note
263   // that true is returned iff this is in fact the user's first add and, if so,
264   // the time it took for the user to add their first item should be recorded.
265   if (holding_space_prefs::MarkTimeOfFirstAdd(profile_->GetPrefs()))
266     RecordTimeFromFirstAvailabilityToFirstAdd(profile_);
267 
268   holding_space_model_.AddItem(std::move(item));
269 }
270 
Shutdown()271 void HoldingSpaceKeyedService::Shutdown() {
272   for (auto& delegate : delegates_)
273     delegate->Shutdown();
274 }
275 
OnProfileAdded(Profile * profile)276 void HoldingSpaceKeyedService::OnProfileAdded(Profile* profile) {
277   if (profile == profile_) {
278     profile_manager_observer_.Remove(GetProfileManager());
279     OnProfileReady();
280   }
281 }
282 
OnProfileReady()283 void HoldingSpaceKeyedService::OnProfileReady() {
284   // The `HoldingSpaceDownloadsDelegate` monitors the status of downloads.
285   delegates_.push_back(std::make_unique<HoldingSpaceDownloadsDelegate>(
286       profile_, &holding_space_model_,
287       /*item_downloaded_callback=*/
288       base::BindRepeating(&HoldingSpaceKeyedService::AddDownload,
289                           weak_factory_.GetWeakPtr())));
290 
291   // The `HoldingSpaceFileSystemDelegate` monitors the file system for changes.
292   delegates_.push_back(std::make_unique<HoldingSpaceFileSystemDelegate>(
293       profile_, &holding_space_model_));
294 
295   // The `HoldingSpacePersistenceDelegate` manages holding space persistence.
296   delegates_.push_back(std::make_unique<HoldingSpacePersistenceDelegate>(
297       profile_, &holding_space_model_, &thumbnail_loader_,
298       /*item_restored_callback=*/
299       base::BindRepeating(&HoldingSpaceKeyedService::AddItem,
300                           weak_factory_.GetWeakPtr()),
301       /*persistence_restored_callback=*/
302       base::BindOnce(&HoldingSpaceKeyedService::OnPersistenceRestored,
303                      weak_factory_.GetWeakPtr())));
304 
305   // Initialize all delegates only after they have been added to our collection.
306   // Delegates should not fire their respective callbacks during construction
307   // but once they have been initialized they are free to do so.
308   for (auto& delegate : delegates_)
309     delegate->Init();
310 }
311 
OnPersistenceRestored()312 void HoldingSpaceKeyedService::OnPersistenceRestored() {
313   for (auto& delegate : delegates_)
314     delegate->NotifyPersistenceRestored();
315 
316   HoldingSpaceController::Get()->RegisterClientAndModelForUser(
317       account_id_, &holding_space_client_, &holding_space_model_);
318 }
319 
320 }  // namespace ash
321