blob: f808d74e35c9d830ea6193e2b90aa6d89428402a [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"
19#include "components/ntp_snippets/pref_names.h"
20#include "components/prefs/pref_registry_simple.h"
21#include "components/prefs/pref_service.h"
pke6dbb90af2016-07-08 14:00:4622#include "ui/gfx/image/image.h"
23
24namespace ntp_snippets {
25
vitaliii45941152016-09-05 08:58:1326ContentSuggestionsService::ContentSuggestionsService(
27 State state,
dgnf5708892016-11-22 10:36:2228 SigninManagerBase* signin_manager,
jkrcale13510e2016-09-08 17:56:2029 history::HistoryService* history_service,
vitaliii7456f5a2016-12-19 11:13:2530 PrefService* pref_service,
jkrcalf9966462017-03-29 16:25:2131 std::unique_ptr<CategoryRanker> category_ranker,
32 std::unique_ptr<UserClassifier> user_classifier,
33 std::unique_ptr<RemoteSuggestionsScheduler> remote_suggestions_scheduler)
jkrcale13510e2016-09-08 17:56:2034 : state_(state),
dgnf5708892016-11-22 10:36:2235 signin_observer_(this),
jkrcale13510e2016-09-08 17:56:2036 history_service_observer_(this),
jkrcal093410c2016-12-21 16:13:5537 remote_suggestions_provider_(nullptr),
dgn52914722016-10-18 10:28:4238 pref_service_(pref_service),
jkrcalf9966462017-03-29 16:25:2139 remote_suggestions_scheduler_(std::move(remote_suggestions_scheduler)),
40 user_classifier_(std::move(user_classifier)),
vitaliii7456f5a2016-12-19 11:13:2541 category_ranker_(std::move(category_ranker)) {
vitaliii45941152016-09-05 08:58:1342 // Can be null in tests.
dgnf5708892016-11-22 10:36:2243 if (signin_manager) {
44 signin_observer_.Add(signin_manager);
45 }
46
vitaliii4408d6c2016-11-21 14:16:2447 if (history_service) {
vitaliii45941152016-09-05 08:58:1348 history_service_observer_.Add(history_service);
vitaliii4408d6c2016-11-21 14:16:2449 }
dgn52914722016-10-18 10:28:4250
51 RestoreDismissedCategoriesFromPrefs();
vitaliii45941152016-09-05 08:58:1352}
pke6dbb90af2016-07-08 14:00:4653
treib62e819e2016-09-27 11:47:3454ContentSuggestionsService::~ContentSuggestionsService() = default;
pke6dbb90af2016-07-08 14:00:4655
56void ContentSuggestionsService::Shutdown() {
jkrcal093410c2016-12-21 16:13:5557 remote_suggestions_provider_ = nullptr;
58 remote_suggestions_scheduler_ = nullptr;
pke5728f082016-08-03 17:27:3559 suggestions_by_category_.clear();
60 providers_by_category_.clear();
61 categories_.clear();
62 providers_.clear();
pke6dbb90af2016-07-08 14:00:4663 state_ = State::DISABLED;
vitaliii4408d6c2016-11-21 14:16:2464 for (Observer& observer : observers_) {
ericwilligers42b92c12016-10-24 20:21:1365 observer.ContentSuggestionsServiceShutdown();
vitaliii4408d6c2016-11-21 14:16:2466 }
pke6dbb90af2016-07-08 14:00:4667}
68
dgn52914722016-10-18 10:28:4269// static
70void ContentSuggestionsService::RegisterProfilePrefs(
71 PrefRegistrySimple* registry) {
72 registry->RegisterListPref(prefs::kDismissedCategories);
73}
74
vitaliii8b5ab282016-12-20 11:06:2275std::vector<Category> ContentSuggestionsService::GetCategories() const {
76 std::vector<Category> sorted_categories = categories_;
77 std::sort(sorted_categories.begin(), sorted_categories.end(),
78 [this](const Category& left, const Category& right) {
79 return category_ranker_->Compare(left, right);
80 });
81 return sorted_categories;
82}
83
pke9c5095ac2016-08-01 13:53:1284CategoryStatus ContentSuggestionsService::GetCategoryStatus(
85 Category category) const {
pke6dbb90af2016-07-08 14:00:4686 if (state_ == State::DISABLED) {
pke9c5095ac2016-08-01 13:53:1287 return CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED;
pke6dbb90af2016-07-08 14:00:4688 }
89
pke4d3a4d62016-08-02 09:06:2190 auto iterator = providers_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:2491 if (iterator == providers_by_category_.end()) {
pke9c5095ac2016-08-01 13:53:1292 return CategoryStatus::NOT_PROVIDED;
vitaliii4408d6c2016-11-21 14:16:2493 }
pke6dbb90af2016-07-08 14:00:4694
95 return iterator->second->GetCategoryStatus(category);
96}
97
pkebd2f650a2016-08-09 14:53:4598base::Optional<CategoryInfo> ContentSuggestionsService::GetCategoryInfo(
99 Category category) const {
100 auto iterator = providers_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:24101 if (iterator == providers_by_category_.end()) {
pkebd2f650a2016-08-09 14:53:45102 return base::Optional<CategoryInfo>();
vitaliii4408d6c2016-11-21 14:16:24103 }
pkebd2f650a2016-08-09 14:53:45104 return iterator->second->GetCategoryInfo(category);
105}
106
pke6dbb90af2016-07-08 14:00:46107const std::vector<ContentSuggestion>&
pke9c5095ac2016-08-01 13:53:12108ContentSuggestionsService::GetSuggestionsForCategory(Category category) const {
pke6dbb90af2016-07-08 14:00:46109 auto iterator = suggestions_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:24110 if (iterator == suggestions_by_category_.end()) {
pke6dbb90af2016-07-08 14:00:46111 return no_suggestions_;
vitaliii4408d6c2016-11-21 14:16:24112 }
pke6dbb90af2016-07-08 14:00:46113 return iterator->second;
114}
115
116void ContentSuggestionsService::FetchSuggestionImage(
treib4bbc54922016-09-28 17:26:44117 const ContentSuggestion::ID& suggestion_id,
pke6dbb90af2016-07-08 14:00:46118 const ImageFetchedCallback& callback) {
treib4bbc54922016-09-28 17:26:44119 if (!providers_by_category_.count(suggestion_id.category())) {
pke6dbb90af2016-07-08 14:00:46120 LOG(WARNING) << "Requested image for suggestion " << suggestion_id
treib4bbc54922016-09-28 17:26:44121 << " for unavailable category " << suggestion_id.category();
pke1da90602016-08-05 14:20:27122 base::ThreadTaskRunnerHandle::Get()->PostTask(
pkef29505d2016-08-26 14:46:34123 FROM_HERE, base::Bind(callback, gfx::Image()));
pke6dbb90af2016-07-08 14:00:46124 return;
125 }
treib4bbc54922016-09-28 17:26:44126 providers_by_category_[suggestion_id.category()]->FetchSuggestionImage(
127 suggestion_id, callback);
pke6dbb90af2016-07-08 14:00:46128}
129
jkrcal10004602017-03-29 07:44:28130void ContentSuggestionsService::FetchSuggestionFavicon(
131 const ContentSuggestion::ID& suggestion_id,
132 int minimum_size_in_pixel,
133 int desired_size_in_pixel,
134 const ImageFetchedCallback& callback) {
135 // TODO(jkrcal): Implement.
136}
137
vitaliii685fdfaa2016-08-31 11:25:46138void ContentSuggestionsService::ClearHistory(
139 base::Time begin,
140 base::Time end,
141 const base::Callback<bool(const GURL& url)>& filter) {
142 for (const auto& provider : providers_) {
143 provider->ClearHistory(begin, end, filter);
144 }
vitaliii6343b2c2017-01-04 07:57:11145 category_ranker_->ClearHistory(begin, end);
tschumann5829c3412017-01-09 21:45:43146 // This potentially removed personalized data which we shouldn't display
147 // anymore.
148 for (Observer& observer : observers_) {
149 observer.OnFullRefreshRequired();
150 }
vitaliii685fdfaa2016-08-31 11:25:46151}
152
treib7d1d7a52016-08-24 14:04:55153void ContentSuggestionsService::ClearAllCachedSuggestions() {
pke6dbb90af2016-07-08 14:00:46154 suggestions_by_category_.clear();
pke151b5502016-08-09 12:15:13155 for (const auto& category_provider_pair : providers_by_category_) {
treib7d1d7a52016-08-24 14:04:55156 category_provider_pair.second->ClearCachedSuggestions(
pke151b5502016-08-09 12:15:13157 category_provider_pair.first);
vitaliii4408d6c2016-11-21 14:16:24158 for (Observer& observer : observers_) {
ericwilligers42b92c12016-10-24 20:21:13159 observer.OnNewSuggestions(category_provider_pair.first);
vitaliii4408d6c2016-11-21 14:16:24160 }
pke6dbb90af2016-07-08 14:00:46161 }
pke6dbb90af2016-07-08 14:00:46162}
163
jkrcal928ed3f2016-09-23 18:47:06164void ContentSuggestionsService::ClearCachedSuggestions(Category category) {
pke151b5502016-08-09 12:15:13165 suggestions_by_category_[category].clear();
166 auto iterator = providers_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:24167 if (iterator != providers_by_category_.end()) {
treib7d1d7a52016-08-24 14:04:55168 iterator->second->ClearCachedSuggestions(category);
vitaliii4408d6c2016-11-21 14:16:24169 }
pke151b5502016-08-09 12:15:13170}
171
pkede0dd9f2016-08-23 09:18:11172void ContentSuggestionsService::GetDismissedSuggestionsForDebugging(
173 Category category,
174 const DismissedSuggestionsCallback& callback) {
pke151b5502016-08-09 12:15:13175 auto iterator = providers_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:24176 if (iterator != providers_by_category_.end()) {
pkede0dd9f2016-08-23 09:18:11177 iterator->second->GetDismissedSuggestionsForDebugging(category, callback);
vitaliii4408d6c2016-11-21 14:16:24178 } else {
pkede0dd9f2016-08-23 09:18:11179 callback.Run(std::vector<ContentSuggestion>());
vitaliii4408d6c2016-11-21 14:16:24180 }
pke151b5502016-08-09 12:15:13181}
182
183void ContentSuggestionsService::ClearDismissedSuggestionsForDebugging(
184 Category category) {
185 auto iterator = providers_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:24186 if (iterator != providers_by_category_.end()) {
pke151b5502016-08-09 12:15:13187 iterator->second->ClearDismissedSuggestionsForDebugging(category);
vitaliii4408d6c2016-11-21 14:16:24188 }
pke6dbb90af2016-07-08 14:00:46189}
190
pke2646c95b2016-07-25 12:18:44191void ContentSuggestionsService::DismissSuggestion(
treib4bbc54922016-09-28 17:26:44192 const ContentSuggestion::ID& suggestion_id) {
193 if (!providers_by_category_.count(suggestion_id.category())) {
pke2646c95b2016-07-25 12:18:44194 LOG(WARNING) << "Dismissed suggestion " << suggestion_id
treib4bbc54922016-09-28 17:26:44195 << " for unavailable category " << suggestion_id.category();
pke6dbb90af2016-07-08 14:00:46196 return;
197 }
treib4bbc54922016-09-28 17:26:44198 providers_by_category_[suggestion_id.category()]->DismissSuggestion(
199 suggestion_id);
pke6dbb90af2016-07-08 14:00:46200
vitaliii2600e8e02016-12-09 17:23:36201 // Remove the suggestion locally if it is present. A suggestion may be missing
202 // localy e.g. if it was sent to UI through |Fetch| or it has been dismissed
203 // from a different NTP.
204 RemoveSuggestionByID(suggestion_id);
pke6dbb90af2016-07-08 14:00:46205}
206
dgn212feea3b2016-09-16 15:08:20207void ContentSuggestionsService::DismissCategory(Category category) {
208 auto providers_it = providers_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:24209 if (providers_it == providers_by_category_.end()) {
dgn212feea3b2016-09-16 15:08:20210 return;
vitaliii4408d6c2016-11-21 14:16:24211 }
dgn212feea3b2016-09-16 15:08:20212
dgn52914722016-10-18 10:28:42213 ContentSuggestionsProvider* provider = providers_it->second;
214 UnregisterCategory(category, provider);
215
216 dismissed_providers_by_category_[category] = provider;
217 StoreDismissedCategoriesToPrefs();
vitaliii3d9423e2017-01-03 17:08:13218
219 category_ranker_->OnCategoryDismissed(category);
dgn212feea3b2016-09-16 15:08:20220}
221
mvanouwerkerk52783d92016-10-12 11:03:40222void ContentSuggestionsService::RestoreDismissedCategories() {
223 // Make a copy as the original will be modified during iteration.
224 auto dismissed_providers_by_category_copy = dismissed_providers_by_category_;
225 for (const auto& category_provider_pair :
226 dismissed_providers_by_category_copy) {
dgn52914722016-10-18 10:28:42227 RestoreDismissedCategory(category_provider_pair.first);
mvanouwerkerk52783d92016-10-12 11:03:40228 }
dgn52914722016-10-18 10:28:42229 StoreDismissedCategoriesToPrefs();
mvanouwerkerk52783d92016-10-12 11:03:40230 DCHECK(dismissed_providers_by_category_.empty());
231}
232
pke6dbb90af2016-07-08 14:00:46233void ContentSuggestionsService::AddObserver(Observer* observer) {
234 observers_.AddObserver(observer);
235}
236
237void ContentSuggestionsService::RemoveObserver(Observer* observer) {
238 observers_.RemoveObserver(observer);
239}
240
241void ContentSuggestionsService::RegisterProvider(
pke5728f082016-08-03 17:27:35242 std::unique_ptr<ContentSuggestionsProvider> provider) {
243 DCHECK(state_ == State::ENABLED);
pke5728f082016-08-03 17:27:35244 providers_.push_back(std::move(provider));
pke6dbb90af2016-07-08 14:00:46245}
246
tschumann83578aa2016-11-03 13:18:32247void ContentSuggestionsService::Fetch(
248 const Category& category,
249 const std::set<std::string>& known_suggestion_ids,
fhorschigd24046c42016-11-09 11:26:25250 const FetchDoneCallback& callback) {
tschumann83578aa2016-11-03 13:18:32251 auto providers_it = providers_by_category_.find(category);
vitaliii4408d6c2016-11-21 14:16:24252 if (providers_it == providers_by_category_.end()) {
tschumann83578aa2016-11-03 13:18:32253 return;
vitaliii4408d6c2016-11-21 14:16:24254 }
tschumann83578aa2016-11-03 13:18:32255
256 providers_it->second->Fetch(category, known_suggestion_ids, callback);
257}
258
jkrcal093410c2016-12-21 16:13:55259void ContentSuggestionsService::ReloadSuggestions() {
260 for (const auto& provider : providers_) {
261 provider->ReloadSuggestions();
262 }
263}
264
dgnb8a8a5c2017-03-31 12:35:36265void ContentSuggestionsService::SetRemoteSuggestionsServiceEnabled(
266 bool enabled) {
267 pref_service_->SetBoolean(prefs::kEnableSnippets, enabled);
268}
269
270bool ContentSuggestionsService::IsRemoteSuggestionsServiceEnabled() const {
271 return pref_service_->GetBoolean(prefs::kEnableSnippets);
272}
273
274bool ContentSuggestionsService::IsRemoteSuggestionsServiceManaged() const {
275 return pref_service_->IsManagedPreference(prefs::kEnableSnippets);
276}
277
278bool ContentSuggestionsService::IsRemoteSuggestionsServiceManagedByCustodian()
279 const {
280 return pref_service_->IsPreferenceManagedByCustodian(prefs::kEnableSnippets);
281}
282
pke6dbb90af2016-07-08 14:00:46283////////////////////////////////////////////////////////////////////////////////
284// Private methods
285
286void ContentSuggestionsService::OnNewSuggestions(
pke4d3a4d62016-08-02 09:06:21287 ContentSuggestionsProvider* provider,
288 Category category,
treib62e819e2016-09-27 11:47:34289 std::vector<ContentSuggestion> suggestions) {
treib534523b2016-10-20 16:19:44290 // Providers shouldn't call this when they're in a non-available state.
291 DCHECK(
292 IsCategoryStatusInitOrAvailable(provider->GetCategoryStatus(category)));
293
dgn52914722016-10-18 10:28:42294 if (TryRegisterProviderForCategory(provider, category)) {
pke4d3a4d62016-08-02 09:06:21295 NotifyCategoryStatusChanged(category);
dgn52914722016-10-18 10:28:42296 } else if (IsCategoryDismissed(category)) {
297 // The category has been registered as a dismissed one. We need to
298 // check if the dismissal can be cleared now that we received new data.
vitaliii4408d6c2016-11-21 14:16:24299 if (suggestions.empty()) {
dgn52914722016-10-18 10:28:42300 return;
vitaliii4408d6c2016-11-21 14:16:24301 }
dgn52914722016-10-18 10:28:42302
303 RestoreDismissedCategory(category);
304 StoreDismissedCategoriesToPrefs();
305
306 NotifyCategoryStatusChanged(category);
307 }
pke3b2e3632016-08-12 12:52:53308
treib39fffa12016-10-14 14:59:10309 if (!IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) {
310 // A provider shouldn't send us suggestions while it's not available.
311 DCHECK(suggestions.empty());
pke3b2e3632016-08-12 12:52:53312 return;
treib39fffa12016-10-14 14:59:10313 }
pke6dbb90af2016-07-08 14:00:46314
treib62e819e2016-09-27 11:47:34315 suggestions_by_category_[category] = std::move(suggestions);
pke6dbb90af2016-07-08 14:00:46316
vitaliii4408d6c2016-11-21 14:16:24317 for (Observer& observer : observers_) {
ericwilligers42b92c12016-10-24 20:21:13318 observer.OnNewSuggestions(category);
vitaliii4408d6c2016-11-21 14:16:24319 }
pke6dbb90af2016-07-08 14:00:46320}
321
322void ContentSuggestionsService::OnCategoryStatusChanged(
pke4d3a4d62016-08-02 09:06:21323 ContentSuggestionsProvider* provider,
324 Category category,
pke9c5095ac2016-08-01 13:53:12325 CategoryStatus new_status) {
pke4d3a4d62016-08-02 09:06:21326 if (new_status == CategoryStatus::NOT_PROVIDED) {
dgn52914722016-10-18 10:28:42327 UnregisterCategory(category, provider);
pke4d3a4d62016-08-02 09:06:21328 } else {
vitaliii4408d6c2016-11-21 14:16:24329 if (!IsCategoryStatusAvailable(new_status)) {
dgn52914722016-10-18 10:28:42330 suggestions_by_category_.erase(category);
vitaliii4408d6c2016-11-21 14:16:24331 }
dgn52914722016-10-18 10:28:42332 TryRegisterProviderForCategory(provider, category);
pke4d3a4d62016-08-02 09:06:21333 DCHECK_EQ(new_status, provider->GetCategoryStatus(category));
334 }
dgn52914722016-10-18 10:28:42335
vitaliii4408d6c2016-11-21 14:16:24336 if (!IsCategoryDismissed(category)) {
dgn52914722016-10-18 10:28:42337 NotifyCategoryStatusChanged(category);
vitaliii4408d6c2016-11-21 14:16:24338 }
pke6dbb90af2016-07-08 14:00:46339}
340
pke2a48f852016-08-18 13:33:52341void ContentSuggestionsService::OnSuggestionInvalidated(
342 ContentSuggestionsProvider* provider,
treib4bbc54922016-09-28 17:26:44343 const ContentSuggestion::ID& suggestion_id) {
344 RemoveSuggestionByID(suggestion_id);
vitaliii4408d6c2016-11-21 14:16:24345 for (Observer& observer : observers_) {
ericwilligers42b92c12016-10-24 20:21:13346 observer.OnSuggestionInvalidated(suggestion_id);
vitaliii4408d6c2016-11-21 14:16:24347 }
pke2a48f852016-08-18 13:33:52348}
349
dgnf5708892016-11-22 10:36:22350// SigninManagerBase::Observer implementation
351void ContentSuggestionsService::GoogleSigninSucceeded(
352 const std::string& account_id,
353 const std::string& username,
354 const std::string& password) {
355 OnSignInStateChanged();
356}
357
358void ContentSuggestionsService::GoogleSignedOut(const std::string& account_id,
359 const std::string& username) {
360 OnSignInStateChanged();
361}
362
vitaliii45941152016-09-05 08:58:13363// history::HistoryServiceObserver implementation.
364void ContentSuggestionsService::OnURLsDeleted(
365 history::HistoryService* history_service,
366 bool all_history,
367 bool expired,
368 const history::URLRows& deleted_rows,
369 const std::set<GURL>& favicon_urls) {
370 // We don't care about expired entries.
vitaliii4408d6c2016-11-21 14:16:24371 if (expired) {
vitaliii45941152016-09-05 08:58:13372 return;
vitaliii4408d6c2016-11-21 14:16:24373 }
vitaliii45941152016-09-05 08:58:13374
vitaliii45941152016-09-05 08:58:13375 if (all_history) {
vitaliii45941152016-09-05 08:58:13376 base::Callback<bool(const GURL& url)> filter =
377 base::Bind([](const GURL& url) { return true; });
tschumann5829c3412017-01-09 21:45:43378 ClearHistory(base::Time(), base::Time::Max(), filter);
vitaliii45941152016-09-05 08:58:13379 } else {
tschumann5829c3412017-01-09 21:45:43380 // If a user deletes a single URL, we don't consider this a clear user
381 // intend to clear our data.
382 // TODO(tschumann): Single URL deletions should be handled on a case-by-case
383 // basis. However this depends on the provider's details and thus cannot be
384 // done here. Introduce a OnURLsDeleted() method on the providers to move
385 // this decision further down.
386 if (deleted_rows.size() < 2) {
vitaliii45941152016-09-05 08:58:13387 return;
vitaliii4408d6c2016-11-21 14:16:24388 }
vitaliii45941152016-09-05 08:58:13389 std::set<GURL> deleted_urls;
390 for (const history::URLRow& row : deleted_rows) {
vitaliii45941152016-09-05 08:58:13391 deleted_urls.insert(row.url());
392 }
sfierae8969bc2017-03-26 18:38:41393 base::Callback<bool(const GURL& url)> filter =
394 base::Bind([](const std::set<GURL>& set,
395 const GURL& url) { return set.count(url) != 0; },
396 deleted_urls);
tschumann5829c3412017-01-09 21:45:43397 // We usually don't have any time-related information (the URLRow objects
398 // usually don't provide a |last_visit()| timestamp. Hence we simply clear
399 // the whole history for the selected URLs.
400 ClearHistory(base::Time(), base::Time::Max(), filter);
vitaliii45941152016-09-05 08:58:13401 }
402}
403
404void ContentSuggestionsService::HistoryServiceBeingDeleted(
405 history::HistoryService* history_service) {
406 history_service_observer_.RemoveAll();
407}
408
dgn52914722016-10-18 10:28:42409bool ContentSuggestionsService::TryRegisterProviderForCategory(
pke4d3a4d62016-08-02 09:06:21410 ContentSuggestionsProvider* provider,
411 Category category) {
412 auto it = providers_by_category_.find(category);
413 if (it != providers_by_category_.end()) {
414 DCHECK_EQ(it->second, provider);
415 return false;
416 }
417
mvanouwerkerk52783d92016-10-12 11:03:40418 auto dismissed_it = dismissed_providers_by_category_.find(category);
419 if (dismissed_it != dismissed_providers_by_category_.end()) {
dgn52914722016-10-18 10:28:42420 // The initialisation of dismissed categories registers them with |nullptr|
421 // for providers, we need to check for that to see if the provider is
422 // already registered or not.
423 if (!dismissed_it->second) {
424 dismissed_it->second = provider;
425 } else {
426 DCHECK_EQ(dismissed_it->second, provider);
427 }
428 return false;
mvanouwerkerk52783d92016-10-12 11:03:40429 }
430
dgn52914722016-10-18 10:28:42431 RegisterCategory(category, provider);
432 return true;
433}
434
435void ContentSuggestionsService::RegisterCategory(
436 Category category,
437 ContentSuggestionsProvider* provider) {
438 DCHECK(!base::ContainsKey(providers_by_category_, category));
439 DCHECK(!IsCategoryDismissed(category));
440
pke4d3a4d62016-08-02 09:06:21441 providers_by_category_[category] = provider;
442 categories_.push_back(category);
pke4d3a4d62016-08-02 09:06:21443 if (IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) {
444 suggestions_by_category_.insert(
445 std::make_pair(category, std::vector<ContentSuggestion>()));
446 }
dgn52914722016-10-18 10:28:42447}
448
449void ContentSuggestionsService::UnregisterCategory(
450 Category category,
451 ContentSuggestionsProvider* provider) {
452 auto providers_it = providers_by_category_.find(category);
453 if (providers_it == providers_by_category_.end()) {
454 DCHECK(IsCategoryDismissed(category));
455 return;
456 }
457
458 DCHECK_EQ(provider, providers_it->second);
459 providers_by_category_.erase(providers_it);
460 categories_.erase(
461 std::find(categories_.begin(), categories_.end(), category));
462 suggestions_by_category_.erase(category);
pke6dbb90af2016-07-08 14:00:46463}
464
pke2a48f852016-08-18 13:33:52465bool ContentSuggestionsService::RemoveSuggestionByID(
treib4bbc54922016-09-28 17:26:44466 const ContentSuggestion::ID& suggestion_id) {
pke2a48f852016-08-18 13:33:52467 std::vector<ContentSuggestion>* suggestions =
treib4bbc54922016-09-28 17:26:44468 &suggestions_by_category_[suggestion_id.category()];
pke2a48f852016-08-18 13:33:52469 auto position =
470 std::find_if(suggestions->begin(), suggestions->end(),
471 [&suggestion_id](const ContentSuggestion& suggestion) {
472 return suggestion_id == suggestion.id();
473 });
vitaliii4408d6c2016-11-21 14:16:24474 if (position == suggestions->end()) {
pke2a48f852016-08-18 13:33:52475 return false;
vitaliii4408d6c2016-11-21 14:16:24476 }
pke2a48f852016-08-18 13:33:52477 suggestions->erase(position);
treib063e6a62016-08-25 11:34:29478
pke2a48f852016-08-18 13:33:52479 return true;
480}
481
pke9c5095ac2016-08-01 13:53:12482void ContentSuggestionsService::NotifyCategoryStatusChanged(Category category) {
vitaliii4408d6c2016-11-21 14:16:24483 for (Observer& observer : observers_) {
ericwilligers42b92c12016-10-24 20:21:13484 observer.OnCategoryStatusChanged(category, GetCategoryStatus(category));
vitaliii4408d6c2016-11-21 14:16:24485 }
pke6dbb90af2016-07-08 14:00:46486}
487
dgnf5708892016-11-22 10:36:22488void ContentSuggestionsService::OnSignInStateChanged() {
489 // First notify the providers, so they can make the required changes.
490 for (const auto& provider : providers_) {
491 provider->OnSignInStateChanged();
492 }
493
494 // Finally notify the observers so they refresh only after the backend is
495 // ready.
496 for (Observer& observer : observers_) {
497 observer.OnFullRefreshRequired();
498 }
499}
500
dgn52914722016-10-18 10:28:42501bool ContentSuggestionsService::IsCategoryDismissed(Category category) const {
502 return base::ContainsKey(dismissed_providers_by_category_, category);
503}
504
505void ContentSuggestionsService::RestoreDismissedCategory(Category category) {
506 auto dismissed_it = dismissed_providers_by_category_.find(category);
507 DCHECK(base::ContainsKey(dismissed_providers_by_category_, category));
508
509 // Keep the reference to the provider and remove it from the dismissed ones,
510 // because the category registration enforces that it's not dismissed.
511 ContentSuggestionsProvider* provider = dismissed_it->second;
512 dismissed_providers_by_category_.erase(dismissed_it);
513
vitaliii4408d6c2016-11-21 14:16:24514 if (provider) {
dgn52914722016-10-18 10:28:42515 RegisterCategory(category, provider);
vitaliii4408d6c2016-11-21 14:16:24516 }
dgn52914722016-10-18 10:28:42517}
518
519void ContentSuggestionsService::RestoreDismissedCategoriesFromPrefs() {
520 // This must only be called at startup.
521 DCHECK(dismissed_providers_by_category_.empty());
522 DCHECK(providers_by_category_.empty());
523
524 const base::ListValue* list =
525 pref_service_->GetList(prefs::kDismissedCategories);
526 for (const std::unique_ptr<base::Value>& entry : *list) {
527 int id = 0;
528 if (!entry->GetAsInteger(&id)) {
529 DLOG(WARNING) << "Invalid category pref value: " << *entry;
530 continue;
531 }
532
533 // When the provider is registered, it will be stored in this map.
vitaliii7456f5a2016-12-19 11:13:25534 dismissed_providers_by_category_[Category::FromIDValue(id)] = nullptr;
dgn52914722016-10-18 10:28:42535 }
536}
537
538void ContentSuggestionsService::StoreDismissedCategoriesToPrefs() {
539 base::ListValue list;
540 for (const auto& category_provider_pair : dismissed_providers_by_category_) {
541 list.AppendInteger(category_provider_pair.first.id());
542 }
543
544 pref_service_->Set(prefs::kDismissedCategories, list);
545}
546
pke6dbb90af2016-07-08 14:00:46547} // namespace ntp_snippets