blob: d8220e0456d1166fcb673fdf55abba480f9f7914 [file] [log] [blame]
pke6dbb90af2016-07-08 14:00:461// Copyright 2016 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 "components/ntp_snippets/content_suggestions_service.h"
6
7#include <algorithm>
8#include <iterator>
vitaliii45941152016-09-05 08:58:139#include <set>
jkrcale13510e2016-09-08 17:56:2010#include <utility>
pke6dbb90af2016-07-08 14:00:4611
12#include "base/bind.h"
pke1da90602016-08-05 14:20:2713#include "base/location.h"
jkrcal27b02c12017-03-21 11:18:2614#include "base/memory/ptr_util.h"
pke6dbb90af2016-07-08 14:00:4615#include "base/strings/string_number_conversions.h"
pke1da90602016-08-05 14:20:2716#include "base/threading/thread_task_runner_handle.h"
jkrcal27b02c12017-03-21 11:18:2617#include "base/time/default_clock.h"
dgn52914722016-10-18 10:28:4218#include "base/values.h"
jkrcal7b7e71f12017-04-06 13:12:4019#include "components/favicon/core/large_icon_service.h"
20#include "components/favicon_base/fallback_icon_style.h"
21#include "components/favicon_base/favicon_types.h"
dgn52914722016-10-18 10:28:4222#include "components/ntp_snippets/pref_names.h"
23#include "components/prefs/pref_registry_simple.h"
24#include "components/prefs/pref_service.h"
pke6dbb90af2016-07-08 14:00:4625#include "ui/gfx/image/image.h"
26
27namespace ntp_snippets {
28
vitaliii45941152016-09-05 08:58:1329ContentSuggestionsService::ContentSuggestionsService(
30 State state,
dgnf5708892016-11-22 10:36:2231 SigninManagerBase* signin_manager,
jkrcale13510e2016-09-08 17:56:2032 history::HistoryService* history_service,
jkrcal7b7e71f12017-04-06 13:12:4033 favicon::LargeIconService* large_icon_service,
vitaliii7456f5a2016-12-19 11:13:2534 PrefService* pref_service,
jkrcalf9966462017-03-29 16:25:2135 std::unique_ptr<CategoryRanker> category_ranker,
36 std::unique_ptr<UserClassifier> user_classifier,
37 std::unique_ptr<RemoteSuggestionsScheduler> remote_suggestions_scheduler)
jkrcale13510e2016-09-08 17:56:2038 : state_(state),
dgnf5708892016-11-22 10:36:2239 signin_observer_(this),
jkrcale13510e2016-09-08 17:56:2040 history_service_observer_(this),
jkrcal093410c2016-12-21 16:13:5541 remote_suggestions_provider_(nullptr),
jkrcal7b7e71f12017-04-06 13:12:4042 large_icon_service_(large_icon_service),
dgn52914722016-10-18 10:28:4243 pref_service_(pref_service),
jkrcalf9966462017-03-29 16:25:2144 remote_suggestions_scheduler_(std::move(remote_suggestions_scheduler)),
45 user_classifier_(std::move(user_classifier)),
vitaliii7456f5a2016-12-19 11:13:2546 category_ranker_(std::move(category_ranker)) {
vitaliii45941152016-09-05 08:58:1347 // Can be null in tests.
dgnf5708892016-11-22 10:36:2248 if (signin_manager) {
49 signin_observer_.Add(signin_manager);
50 }
51
vitaliii4408d6c2016-11-21 14:16:2452 if (history_service) {
vitaliii45941152016-09-05 08:58:1353 history_service_observer_.Add(history_service);
vitaliii4408d6c2016-11-21 14:16:2454 }
dgn52914722016-10-18 10:28:4255
56 RestoreDismissedCategoriesFromPrefs();
vitaliii45941152016-09-05 08:58:1357}
pke6dbb90af2016-07-08 14:00:4658
treib62e819e2016-09-27 11:47:3459ContentSuggestionsService::~ContentSuggestionsService() = default;
pke6dbb90af2016-07-08 14:00:4660
61void ContentSuggestionsService::Shutdown() {
jkrcal093410c2016-12-21 16:13:5562 remote_suggestions_provider_ = nullptr;
63 remote_suggestions_scheduler_ = nullptr;
pke5728f082016-08-03 17:27:3564 suggestions_by_category_.clear();
65 providers_by_category_.clear();
66 categories_.clear();
67 providers_.clear();
pke6dbb90af2016-07-08 14:00:4668 state_ = State::DISABLED;
vitaliii4408d6c2016-11-21 14:16:2469 for (Observer& observer : observers_) {
ericwilligers42b92c12016-10-24 20:21:1370 observer.ContentSuggestionsServiceShutdown();
vitaliii4408d6c2016-11-21 14:16:2471 }
pke6dbb90af2016-07-08 14:00:4672}
73
dgn52914722016-10-18 10:28:4274// static
75void ContentSuggestionsService::RegisterProfilePrefs(
76 PrefRegistrySimple* registry) {
77 registry->RegisterListPref(prefs::kDismissedCategories);
78}
79
vitaliii8b5ab282016-12-20 11:06:2280std::vector<Category> ContentSuggestionsService::GetCategories() const {
81 std::vector<Category> sorted_categories = categories_;
82 std::sort(sorted_categories.begin(), sorted_categories.end(),
83 [this](const Category& left, const Category& right) {
84 return category_ranker_->Compare(left, right);
85 });
86 return sorted_categories;
87}
88
pke9c5095ac2016-08-01 13:53:1289CategoryStatus ContentSuggestionsService::GetCategoryStatus(
90 Category category) const {
pke6dbb90af2016-07-08 14:00:4691 if (state_ == State::DISABLED) {
pke9c5095ac2016-08-01 13:53:1292 return CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED;
pke6dbb90af2016-07-08 14:00:4693 }
94
pke4d3a4d62016-08-02 09:06:2195 auto iterator = providers_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:2496 if (iterator == providers_by_category_.end()) {
pke9c5095ac2016-08-01 13:53:1297 return CategoryStatus::NOT_PROVIDED;
vitaliii4408d6c2016-11-21 14:16:2498 }
pke6dbb90af2016-07-08 14:00:4699
100 return iterator->second->GetCategoryStatus(category);
101}
102
pkebd2f650a2016-08-09 14:53:45103base::Optional<CategoryInfo> ContentSuggestionsService::GetCategoryInfo(
104 Category category) const {
105 auto iterator = providers_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:24106 if (iterator == providers_by_category_.end()) {
pkebd2f650a2016-08-09 14:53:45107 return base::Optional<CategoryInfo>();
vitaliii4408d6c2016-11-21 14:16:24108 }
pkebd2f650a2016-08-09 14:53:45109 return iterator->second->GetCategoryInfo(category);
110}
111
pke6dbb90af2016-07-08 14:00:46112const std::vector<ContentSuggestion>&
pke9c5095ac2016-08-01 13:53:12113ContentSuggestionsService::GetSuggestionsForCategory(Category category) const {
pke6dbb90af2016-07-08 14:00:46114 auto iterator = suggestions_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:24115 if (iterator == suggestions_by_category_.end()) {
pke6dbb90af2016-07-08 14:00:46116 return no_suggestions_;
vitaliii4408d6c2016-11-21 14:16:24117 }
pke6dbb90af2016-07-08 14:00:46118 return iterator->second;
119}
120
121void ContentSuggestionsService::FetchSuggestionImage(
treib4bbc54922016-09-28 17:26:44122 const ContentSuggestion::ID& suggestion_id,
pke6dbb90af2016-07-08 14:00:46123 const ImageFetchedCallback& callback) {
treib4bbc54922016-09-28 17:26:44124 if (!providers_by_category_.count(suggestion_id.category())) {
pke6dbb90af2016-07-08 14:00:46125 LOG(WARNING) << "Requested image for suggestion " << suggestion_id
treib4bbc54922016-09-28 17:26:44126 << " for unavailable category " << suggestion_id.category();
pke1da90602016-08-05 14:20:27127 base::ThreadTaskRunnerHandle::Get()->PostTask(
pkef29505d2016-08-26 14:46:34128 FROM_HERE, base::Bind(callback, gfx::Image()));
pke6dbb90af2016-07-08 14:00:46129 return;
130 }
treib4bbc54922016-09-28 17:26:44131 providers_by_category_[suggestion_id.category()]->FetchSuggestionImage(
132 suggestion_id, callback);
pke6dbb90af2016-07-08 14:00:46133}
134
jkrcalcd011682017-04-13 05:41:16135// TODO(jkrcal): Split the favicon fetching into a separate class.
jkrcal10004602017-03-29 07:44:28136void ContentSuggestionsService::FetchSuggestionFavicon(
137 const ContentSuggestion::ID& suggestion_id,
138 int minimum_size_in_pixel,
139 int desired_size_in_pixel,
140 const ImageFetchedCallback& callback) {
jkrcal7b7e71f12017-04-06 13:12:40141 std::vector<ContentSuggestion>* suggestions =
142 &suggestions_by_category_[suggestion_id.category()];
143 auto position =
144 std::find_if(suggestions->begin(), suggestions->end(),
145 [&suggestion_id](const ContentSuggestion& suggestion) {
146 return suggestion_id == suggestion.id();
147 });
148 if (position == suggestions->end() || !large_icon_service_) {
149 base::ThreadTaskRunnerHandle::Get()->PostTask(
150 FROM_HERE, base::Bind(callback, gfx::Image()));
151 return;
152 }
153
jkrcalcd011682017-04-13 05:41:16154 const GURL& domain_with_favicon =
155 position->url_with_favicon().GetWithEmptyPath();
jkrcal7b7e71f12017-04-06 13:12:40156
157 // TODO(jkrcal): Create a general wrapper function in LargeIconService that
158 // does handle the get-from-cache-and-fallback-to-google-server functionality
159 // in one shot (for all clients that do not need to react in between).
160 large_icon_service_->GetLargeIconImageOrFallbackStyle(
jkrcalcd011682017-04-13 05:41:16161 domain_with_favicon, minimum_size_in_pixel, desired_size_in_pixel,
jkrcal7b7e71f12017-04-06 13:12:40162 base::Bind(&ContentSuggestionsService::OnGetFaviconFromCacheFinished,
jkrcalcd011682017-04-13 05:41:16163 base::Unretained(this), domain_with_favicon,
164 minimum_size_in_pixel, desired_size_in_pixel, callback,
jkrcal7b7e71f12017-04-06 13:12:40165 /*continue_to_google_server=*/true),
166 &favicons_task_tracker_);
167}
168
169void ContentSuggestionsService::OnGetFaviconFromCacheFinished(
170 const GURL& publisher_url,
171 int minimum_size_in_pixel,
172 int desired_size_in_pixel,
173 const ImageFetchedCallback& callback,
174 bool continue_to_google_server,
175 const favicon_base::LargeIconImageResult& result) {
176 if (!result.image.IsEmpty()) {
177 callback.Run(result.image);
178 return;
179 }
180
181 if (!continue_to_google_server ||
182 (result.fallback_icon_style &&
183 !result.fallback_icon_style->is_default_background_color)) {
184 // We cannot download from the server if there is some small icon in the
185 // cache (resulting in non-default bakground color) or if we already did so.
186 callback.Run(gfx::Image());
187 return;
188 }
189
190 // Try to fetch the favicon from a Google favicon server.
191 large_icon_service_
192 ->GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(
193 publisher_url, minimum_size_in_pixel,
194 base::Bind(
195 &ContentSuggestionsService::OnGetFaviconFromGoogleServerFinished,
196 base::Unretained(this), publisher_url, minimum_size_in_pixel,
197 desired_size_in_pixel, callback));
198}
199
200void ContentSuggestionsService::OnGetFaviconFromGoogleServerFinished(
201 const GURL& publisher_url,
202 int minimum_size_in_pixel,
203 int desired_size_in_pixel,
204 const ImageFetchedCallback& callback,
205 bool success) {
206 if (!success) {
207 callback.Run(gfx::Image());
208 return;
209 }
210
211 // Get the freshly downloaded icon from the cache.
212 large_icon_service_->GetLargeIconImageOrFallbackStyle(
213 publisher_url, minimum_size_in_pixel, desired_size_in_pixel,
214 base::Bind(&ContentSuggestionsService::OnGetFaviconFromCacheFinished,
215 base::Unretained(this), publisher_url, minimum_size_in_pixel,
216 desired_size_in_pixel, callback,
217 /*continue_to_google_server=*/false),
218 &favicons_task_tracker_);
jkrcal10004602017-03-29 07:44:28219}
220
vitaliii685fdfaa2016-08-31 11:25:46221void ContentSuggestionsService::ClearHistory(
222 base::Time begin,
223 base::Time end,
224 const base::Callback<bool(const GURL& url)>& filter) {
225 for (const auto& provider : providers_) {
226 provider->ClearHistory(begin, end, filter);
227 }
vitaliii6343b2c2017-01-04 07:57:11228 category_ranker_->ClearHistory(begin, end);
tschumann5829c3412017-01-09 21:45:43229 // This potentially removed personalized data which we shouldn't display
230 // anymore.
231 for (Observer& observer : observers_) {
232 observer.OnFullRefreshRequired();
233 }
vitaliii685fdfaa2016-08-31 11:25:46234}
235
treib7d1d7a52016-08-24 14:04:55236void ContentSuggestionsService::ClearAllCachedSuggestions() {
pke6dbb90af2016-07-08 14:00:46237 suggestions_by_category_.clear();
pke151b5502016-08-09 12:15:13238 for (const auto& category_provider_pair : providers_by_category_) {
treib7d1d7a52016-08-24 14:04:55239 category_provider_pair.second->ClearCachedSuggestions(
pke151b5502016-08-09 12:15:13240 category_provider_pair.first);
vitaliii4408d6c2016-11-21 14:16:24241 for (Observer& observer : observers_) {
ericwilligers42b92c12016-10-24 20:21:13242 observer.OnNewSuggestions(category_provider_pair.first);
vitaliii4408d6c2016-11-21 14:16:24243 }
pke6dbb90af2016-07-08 14:00:46244 }
pke6dbb90af2016-07-08 14:00:46245}
246
jkrcal928ed3f2016-09-23 18:47:06247void ContentSuggestionsService::ClearCachedSuggestions(Category category) {
pke151b5502016-08-09 12:15:13248 suggestions_by_category_[category].clear();
249 auto iterator = providers_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:24250 if (iterator != providers_by_category_.end()) {
treib7d1d7a52016-08-24 14:04:55251 iterator->second->ClearCachedSuggestions(category);
vitaliii4408d6c2016-11-21 14:16:24252 }
pke151b5502016-08-09 12:15:13253}
254
pkede0dd9f2016-08-23 09:18:11255void ContentSuggestionsService::GetDismissedSuggestionsForDebugging(
256 Category category,
257 const DismissedSuggestionsCallback& callback) {
pke151b5502016-08-09 12:15:13258 auto iterator = providers_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:24259 if (iterator != providers_by_category_.end()) {
pkede0dd9f2016-08-23 09:18:11260 iterator->second->GetDismissedSuggestionsForDebugging(category, callback);
vitaliii4408d6c2016-11-21 14:16:24261 } else {
pkede0dd9f2016-08-23 09:18:11262 callback.Run(std::vector<ContentSuggestion>());
vitaliii4408d6c2016-11-21 14:16:24263 }
pke151b5502016-08-09 12:15:13264}
265
266void ContentSuggestionsService::ClearDismissedSuggestionsForDebugging(
267 Category category) {
268 auto iterator = providers_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:24269 if (iterator != providers_by_category_.end()) {
pke151b5502016-08-09 12:15:13270 iterator->second->ClearDismissedSuggestionsForDebugging(category);
vitaliii4408d6c2016-11-21 14:16:24271 }
pke6dbb90af2016-07-08 14:00:46272}
273
pke2646c95b2016-07-25 12:18:44274void ContentSuggestionsService::DismissSuggestion(
treib4bbc54922016-09-28 17:26:44275 const ContentSuggestion::ID& suggestion_id) {
276 if (!providers_by_category_.count(suggestion_id.category())) {
pke2646c95b2016-07-25 12:18:44277 LOG(WARNING) << "Dismissed suggestion " << suggestion_id
treib4bbc54922016-09-28 17:26:44278 << " for unavailable category " << suggestion_id.category();
pke6dbb90af2016-07-08 14:00:46279 return;
280 }
treib4bbc54922016-09-28 17:26:44281 providers_by_category_[suggestion_id.category()]->DismissSuggestion(
282 suggestion_id);
pke6dbb90af2016-07-08 14:00:46283
vitaliii2600e8e02016-12-09 17:23:36284 // Remove the suggestion locally if it is present. A suggestion may be missing
285 // localy e.g. if it was sent to UI through |Fetch| or it has been dismissed
286 // from a different NTP.
287 RemoveSuggestionByID(suggestion_id);
pke6dbb90af2016-07-08 14:00:46288}
289
dgn212feea3b2016-09-16 15:08:20290void ContentSuggestionsService::DismissCategory(Category category) {
291 auto providers_it = providers_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:24292 if (providers_it == providers_by_category_.end()) {
dgn212feea3b2016-09-16 15:08:20293 return;
vitaliii4408d6c2016-11-21 14:16:24294 }
dgn212feea3b2016-09-16 15:08:20295
dgn52914722016-10-18 10:28:42296 ContentSuggestionsProvider* provider = providers_it->second;
297 UnregisterCategory(category, provider);
298
299 dismissed_providers_by_category_[category] = provider;
300 StoreDismissedCategoriesToPrefs();
vitaliii3d9423e2017-01-03 17:08:13301
302 category_ranker_->OnCategoryDismissed(category);
dgn212feea3b2016-09-16 15:08:20303}
304
mvanouwerkerk52783d92016-10-12 11:03:40305void ContentSuggestionsService::RestoreDismissedCategories() {
306 // Make a copy as the original will be modified during iteration.
307 auto dismissed_providers_by_category_copy = dismissed_providers_by_category_;
308 for (const auto& category_provider_pair :
309 dismissed_providers_by_category_copy) {
dgn52914722016-10-18 10:28:42310 RestoreDismissedCategory(category_provider_pair.first);
mvanouwerkerk52783d92016-10-12 11:03:40311 }
dgn52914722016-10-18 10:28:42312 StoreDismissedCategoriesToPrefs();
mvanouwerkerk52783d92016-10-12 11:03:40313 DCHECK(dismissed_providers_by_category_.empty());
314}
315
pke6dbb90af2016-07-08 14:00:46316void ContentSuggestionsService::AddObserver(Observer* observer) {
317 observers_.AddObserver(observer);
318}
319
320void ContentSuggestionsService::RemoveObserver(Observer* observer) {
321 observers_.RemoveObserver(observer);
322}
323
324void ContentSuggestionsService::RegisterProvider(
pke5728f082016-08-03 17:27:35325 std::unique_ptr<ContentSuggestionsProvider> provider) {
326 DCHECK(state_ == State::ENABLED);
pke5728f082016-08-03 17:27:35327 providers_.push_back(std::move(provider));
pke6dbb90af2016-07-08 14:00:46328}
329
tschumann83578aa2016-11-03 13:18:32330void ContentSuggestionsService::Fetch(
331 const Category& category,
332 const std::set<std::string>& known_suggestion_ids,
fhorschigd24046c42016-11-09 11:26:25333 const FetchDoneCallback& callback) {
tschumann83578aa2016-11-03 13:18:32334 auto providers_it = providers_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:24335 if (providers_it == providers_by_category_.end()) {
tschumann83578aa2016-11-03 13:18:32336 return;
vitaliii4408d6c2016-11-21 14:16:24337 }
tschumann83578aa2016-11-03 13:18:32338
339 providers_it->second->Fetch(category, known_suggestion_ids, callback);
340}
341
jkrcal093410c2016-12-21 16:13:55342void ContentSuggestionsService::ReloadSuggestions() {
343 for (const auto& provider : providers_) {
344 provider->ReloadSuggestions();
345 }
346}
347
dgn65d6cd82017-04-11 00:36:36348void ContentSuggestionsService::SetRemoteSuggestionsEnabled(bool enabled) {
dgnb8a8a5c2017-03-31 12:35:36349 pref_service_->SetBoolean(prefs::kEnableSnippets, enabled);
350}
351
dgn65d6cd82017-04-11 00:36:36352bool ContentSuggestionsService::AreRemoteSuggestionsEnabled() const {
dgnb8a8a5c2017-03-31 12:35:36353 return pref_service_->GetBoolean(prefs::kEnableSnippets);
354}
355
dgn65d6cd82017-04-11 00:36:36356bool ContentSuggestionsService::AreRemoteSuggestionsManaged() const {
dgnb8a8a5c2017-03-31 12:35:36357 return pref_service_->IsManagedPreference(prefs::kEnableSnippets);
358}
359
dgn65d6cd82017-04-11 00:36:36360bool ContentSuggestionsService::AreRemoteSuggestionsManagedByCustodian() const {
dgnb8a8a5c2017-03-31 12:35:36361 return pref_service_->IsPreferenceManagedByCustodian(prefs::kEnableSnippets);
362}
363
pke6dbb90af2016-07-08 14:00:46364////////////////////////////////////////////////////////////////////////////////
365// Private methods
366
367void ContentSuggestionsService::OnNewSuggestions(
pke4d3a4d62016-08-02 09:06:21368 ContentSuggestionsProvider* provider,
369 Category category,
treib62e819e2016-09-27 11:47:34370 std::vector<ContentSuggestion> suggestions) {
treib534523b2016-10-20 16:19:44371 // Providers shouldn't call this when they're in a non-available state.
372 DCHECK(
373 IsCategoryStatusInitOrAvailable(provider->GetCategoryStatus(category)));
374
dgn52914722016-10-18 10:28:42375 if (TryRegisterProviderForCategory(provider, category)) {
pke4d3a4d62016-08-02 09:06:21376 NotifyCategoryStatusChanged(category);
dgn52914722016-10-18 10:28:42377 } else if (IsCategoryDismissed(category)) {
378 // The category has been registered as a dismissed one. We need to
379 // check if the dismissal can be cleared now that we received new data.
vitaliii4408d6c2016-11-21 14:16:24380 if (suggestions.empty()) {
dgn52914722016-10-18 10:28:42381 return;
vitaliii4408d6c2016-11-21 14:16:24382 }
dgn52914722016-10-18 10:28:42383
384 RestoreDismissedCategory(category);
385 StoreDismissedCategoriesToPrefs();
386
387 NotifyCategoryStatusChanged(category);
388 }
pke3b2e3632016-08-12 12:52:53389
treib39fffa12016-10-14 14:59:10390 if (!IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) {
391 // A provider shouldn't send us suggestions while it's not available.
392 DCHECK(suggestions.empty());
pke3b2e3632016-08-12 12:52:53393 return;
treib39fffa12016-10-14 14:59:10394 }
pke6dbb90af2016-07-08 14:00:46395
treib62e819e2016-09-27 11:47:34396 suggestions_by_category_[category] = std::move(suggestions);
pke6dbb90af2016-07-08 14:00:46397
vitaliii4408d6c2016-11-21 14:16:24398 for (Observer& observer : observers_) {
ericwilligers42b92c12016-10-24 20:21:13399 observer.OnNewSuggestions(category);
vitaliii4408d6c2016-11-21 14:16:24400 }
pke6dbb90af2016-07-08 14:00:46401}
402
403void ContentSuggestionsService::OnCategoryStatusChanged(
pke4d3a4d62016-08-02 09:06:21404 ContentSuggestionsProvider* provider,
405 Category category,
pke9c5095ac2016-08-01 13:53:12406 CategoryStatus new_status) {
pke4d3a4d62016-08-02 09:06:21407 if (new_status == CategoryStatus::NOT_PROVIDED) {
dgn52914722016-10-18 10:28:42408 UnregisterCategory(category, provider);
pke4d3a4d62016-08-02 09:06:21409 } else {
vitaliii4408d6c2016-11-21 14:16:24410 if (!IsCategoryStatusAvailable(new_status)) {
dgn52914722016-10-18 10:28:42411 suggestions_by_category_.erase(category);
vitaliii4408d6c2016-11-21 14:16:24412 }
dgn52914722016-10-18 10:28:42413 TryRegisterProviderForCategory(provider, category);
pke4d3a4d62016-08-02 09:06:21414 DCHECK_EQ(new_status, provider->GetCategoryStatus(category));
415 }
dgn52914722016-10-18 10:28:42416
vitaliii4408d6c2016-11-21 14:16:24417 if (!IsCategoryDismissed(category)) {
dgn52914722016-10-18 10:28:42418 NotifyCategoryStatusChanged(category);
vitaliii4408d6c2016-11-21 14:16:24419 }
pke6dbb90af2016-07-08 14:00:46420}
421
pke2a48f852016-08-18 13:33:52422void ContentSuggestionsService::OnSuggestionInvalidated(
423 ContentSuggestionsProvider* provider,
treib4bbc54922016-09-28 17:26:44424 const ContentSuggestion::ID& suggestion_id) {
425 RemoveSuggestionByID(suggestion_id);
vitaliii4408d6c2016-11-21 14:16:24426 for (Observer& observer : observers_) {
ericwilligers42b92c12016-10-24 20:21:13427 observer.OnSuggestionInvalidated(suggestion_id);
vitaliii4408d6c2016-11-21 14:16:24428 }
pke2a48f852016-08-18 13:33:52429}
430
dgnf5708892016-11-22 10:36:22431// SigninManagerBase::Observer implementation
432void ContentSuggestionsService::GoogleSigninSucceeded(
433 const std::string& account_id,
434 const std::string& username,
435 const std::string& password) {
436 OnSignInStateChanged();
437}
438
439void ContentSuggestionsService::GoogleSignedOut(const std::string& account_id,
440 const std::string& username) {
441 OnSignInStateChanged();
442}
443
vitaliii45941152016-09-05 08:58:13444// history::HistoryServiceObserver implementation.
445void ContentSuggestionsService::OnURLsDeleted(
446 history::HistoryService* history_service,
447 bool all_history,
448 bool expired,
449 const history::URLRows& deleted_rows,
450 const std::set<GURL>& favicon_urls) {
451 // We don't care about expired entries.
vitaliii4408d6c2016-11-21 14:16:24452 if (expired) {
vitaliii45941152016-09-05 08:58:13453 return;
vitaliii4408d6c2016-11-21 14:16:24454 }
vitaliii45941152016-09-05 08:58:13455
vitaliii45941152016-09-05 08:58:13456 if (all_history) {
vitaliii45941152016-09-05 08:58:13457 base::Callback<bool(const GURL& url)> filter =
458 base::Bind([](const GURL& url) { return true; });
tschumann5829c3412017-01-09 21:45:43459 ClearHistory(base::Time(), base::Time::Max(), filter);
vitaliii45941152016-09-05 08:58:13460 } else {
tschumann5829c3412017-01-09 21:45:43461 // If a user deletes a single URL, we don't consider this a clear user
462 // intend to clear our data.
463 // TODO(tschumann): Single URL deletions should be handled on a case-by-case
464 // basis. However this depends on the provider's details and thus cannot be
465 // done here. Introduce a OnURLsDeleted() method on the providers to move
466 // this decision further down.
467 if (deleted_rows.size() < 2) {
vitaliii45941152016-09-05 08:58:13468 return;
vitaliii4408d6c2016-11-21 14:16:24469 }
vitaliii45941152016-09-05 08:58:13470 std::set<GURL> deleted_urls;
471 for (const history::URLRow& row : deleted_rows) {
vitaliii45941152016-09-05 08:58:13472 deleted_urls.insert(row.url());
473 }
sfierae8969bc2017-03-26 18:38:41474 base::Callback<bool(const GURL& url)> filter =
475 base::Bind([](const std::set<GURL>& set,
476 const GURL& url) { return set.count(url) != 0; },
477 deleted_urls);
tschumann5829c3412017-01-09 21:45:43478 // We usually don't have any time-related information (the URLRow objects
479 // usually don't provide a |last_visit()| timestamp. Hence we simply clear
480 // the whole history for the selected URLs.
481 ClearHistory(base::Time(), base::Time::Max(), filter);
vitaliii45941152016-09-05 08:58:13482 }
483}
484
485void ContentSuggestionsService::HistoryServiceBeingDeleted(
486 history::HistoryService* history_service) {
487 history_service_observer_.RemoveAll();
488}
489
dgn52914722016-10-18 10:28:42490bool ContentSuggestionsService::TryRegisterProviderForCategory(
pke4d3a4d62016-08-02 09:06:21491 ContentSuggestionsProvider* provider,
492 Category category) {
493 auto it = providers_by_category_.find(category);
494 if (it != providers_by_category_.end()) {
495 DCHECK_EQ(it->second, provider);
496 return false;
497 }
498
mvanouwerkerk52783d92016-10-12 11:03:40499 auto dismissed_it = dismissed_providers_by_category_.find(category);
500 if (dismissed_it != dismissed_providers_by_category_.end()) {
dgn52914722016-10-18 10:28:42501 // The initialisation of dismissed categories registers them with |nullptr|
502 // for providers, we need to check for that to see if the provider is
503 // already registered or not.
504 if (!dismissed_it->second) {
505 dismissed_it->second = provider;
506 } else {
507 DCHECK_EQ(dismissed_it->second, provider);
508 }
509 return false;
mvanouwerkerk52783d92016-10-12 11:03:40510 }
511
dgn52914722016-10-18 10:28:42512 RegisterCategory(category, provider);
513 return true;
514}
515
516void ContentSuggestionsService::RegisterCategory(
517 Category category,
518 ContentSuggestionsProvider* provider) {
519 DCHECK(!base::ContainsKey(providers_by_category_, category));
520 DCHECK(!IsCategoryDismissed(category));
521
pke4d3a4d62016-08-02 09:06:21522 providers_by_category_[category] = provider;
523 categories_.push_back(category);
pke4d3a4d62016-08-02 09:06:21524 if (IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) {
525 suggestions_by_category_.insert(
526 std::make_pair(category, std::vector<ContentSuggestion>()));
527 }
dgn52914722016-10-18 10:28:42528}
529
530void ContentSuggestionsService::UnregisterCategory(
531 Category category,
532 ContentSuggestionsProvider* provider) {
533 auto providers_it = providers_by_category_.find(category);
534 if (providers_it == providers_by_category_.end()) {
535 DCHECK(IsCategoryDismissed(category));
536 return;
537 }
538
539 DCHECK_EQ(provider, providers_it->second);
540 providers_by_category_.erase(providers_it);
541 categories_.erase(
542 std::find(categories_.begin(), categories_.end(), category));
543 suggestions_by_category_.erase(category);
pke6dbb90af2016-07-08 14:00:46544}
545
pke2a48f852016-08-18 13:33:52546bool ContentSuggestionsService::RemoveSuggestionByID(
treib4bbc54922016-09-28 17:26:44547 const ContentSuggestion::ID& suggestion_id) {
pke2a48f852016-08-18 13:33:52548 std::vector<ContentSuggestion>* suggestions =
treib4bbc54922016-09-28 17:26:44549 &suggestions_by_category_[suggestion_id.category()];
pke2a48f852016-08-18 13:33:52550 auto position =
551 std::find_if(suggestions->begin(), suggestions->end(),
552 [&suggestion_id](const ContentSuggestion& suggestion) {
553 return suggestion_id == suggestion.id();
554 });
vitaliii4408d6c2016-11-21 14:16:24555 if (position == suggestions->end()) {
pke2a48f852016-08-18 13:33:52556 return false;
vitaliii4408d6c2016-11-21 14:16:24557 }
pke2a48f852016-08-18 13:33:52558 suggestions->erase(position);
treib063e6a62016-08-25 11:34:29559
pke2a48f852016-08-18 13:33:52560 return true;
561}
562
pke9c5095ac2016-08-01 13:53:12563void ContentSuggestionsService::NotifyCategoryStatusChanged(Category category) {
vitaliii4408d6c2016-11-21 14:16:24564 for (Observer& observer : observers_) {
ericwilligers42b92c12016-10-24 20:21:13565 observer.OnCategoryStatusChanged(category, GetCategoryStatus(category));
vitaliii4408d6c2016-11-21 14:16:24566 }
pke6dbb90af2016-07-08 14:00:46567}
568
dgnf5708892016-11-22 10:36:22569void ContentSuggestionsService::OnSignInStateChanged() {
570 // First notify the providers, so they can make the required changes.
571 for (const auto& provider : providers_) {
572 provider->OnSignInStateChanged();
573 }
574
575 // Finally notify the observers so they refresh only after the backend is
576 // ready.
577 for (Observer& observer : observers_) {
578 observer.OnFullRefreshRequired();
579 }
580}
581
dgn52914722016-10-18 10:28:42582bool ContentSuggestionsService::IsCategoryDismissed(Category category) const {
583 return base::ContainsKey(dismissed_providers_by_category_, category);
584}
585
586void ContentSuggestionsService::RestoreDismissedCategory(Category category) {
587 auto dismissed_it = dismissed_providers_by_category_.find(category);
588 DCHECK(base::ContainsKey(dismissed_providers_by_category_, category));
589
590 // Keep the reference to the provider and remove it from the dismissed ones,
591 // because the category registration enforces that it's not dismissed.
592 ContentSuggestionsProvider* provider = dismissed_it->second;
593 dismissed_providers_by_category_.erase(dismissed_it);
594
vitaliii4408d6c2016-11-21 14:16:24595 if (provider) {
dgn52914722016-10-18 10:28:42596 RegisterCategory(category, provider);
vitaliii4408d6c2016-11-21 14:16:24597 }
dgn52914722016-10-18 10:28:42598}
599
600void ContentSuggestionsService::RestoreDismissedCategoriesFromPrefs() {
601 // This must only be called at startup.
602 DCHECK(dismissed_providers_by_category_.empty());
603 DCHECK(providers_by_category_.empty());
604
605 const base::ListValue* list =
606 pref_service_->GetList(prefs::kDismissedCategories);
jdoerriea5676c62017-04-11 18:09:14607 for (const base::Value& entry : *list) {
dgn52914722016-10-18 10:28:42608 int id = 0;
jdoerriea5676c62017-04-11 18:09:14609 if (!entry.GetAsInteger(&id)) {
610 DLOG(WARNING) << "Invalid category pref value: " << entry;
dgn52914722016-10-18 10:28:42611 continue;
612 }
613
614 // When the provider is registered, it will be stored in this map.
vitaliii7456f5a2016-12-19 11:13:25615 dismissed_providers_by_category_[Category::FromIDValue(id)] = nullptr;
dgn52914722016-10-18 10:28:42616 }
617}
618
619void ContentSuggestionsService::StoreDismissedCategoriesToPrefs() {
620 base::ListValue list;
621 for (const auto& category_provider_pair : dismissed_providers_by_category_) {
622 list.AppendInteger(category_provider_pair.first.id());
623 }
624
625 pref_service_->Set(prefs::kDismissedCategories, list);
626}
627
pke6dbb90af2016-07-08 14:00:46628} // namespace ntp_snippets