Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(557)

Side by Side Diff: components/ntp_snippets/content_suggestions_service.cc

Issue 2406573002: 📰 Persist category dismissals (Closed)
Patch Set: fix nits Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/ntp_snippets/content_suggestions_service.h" 5 #include "components/ntp_snippets/content_suggestions_service.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <iterator> 8 #include <iterator>
9 #include <set> 9 #include <set>
10 #include <utility> 10 #include <utility>
11 11
12 #include "base/bind.h" 12 #include "base/bind.h"
13 #include "base/location.h" 13 #include "base/location.h"
14 #include "base/strings/string_number_conversions.h" 14 #include "base/strings/string_number_conversions.h"
15 #include "base/threading/thread_task_runner_handle.h" 15 #include "base/threading/thread_task_runner_handle.h"
16 #include "base/values.h"
17 #include "components/ntp_snippets/pref_names.h"
18 #include "components/prefs/pref_registry_simple.h"
19 #include "components/prefs/pref_service.h"
16 #include "ui/gfx/image/image.h" 20 #include "ui/gfx/image/image.h"
17 21
18 namespace ntp_snippets { 22 namespace ntp_snippets {
19 23
20 ContentSuggestionsService::ContentSuggestionsService( 24 ContentSuggestionsService::ContentSuggestionsService(
21 State state, 25 State state,
22 history::HistoryService* history_service, 26 history::HistoryService* history_service,
23 PrefService* pref_service) 27 PrefService* pref_service)
24 : state_(state), 28 : state_(state),
25 history_service_observer_(this), 29 history_service_observer_(this),
26 ntp_snippets_service_(nullptr), 30 ntp_snippets_service_(nullptr),
31 pref_service_(pref_service),
27 user_classifier_(pref_service) { 32 user_classifier_(pref_service) {
28 // Can be null in tests. 33 // Can be null in tests.
29 if (history_service) 34 if (history_service)
30 history_service_observer_.Add(history_service); 35 history_service_observer_.Add(history_service);
36
37 RestoreDismissedCategoriesFromPrefs();
31 } 38 }
32 39
33 ContentSuggestionsService::~ContentSuggestionsService() = default; 40 ContentSuggestionsService::~ContentSuggestionsService() = default;
34 41
35 void ContentSuggestionsService::Shutdown() { 42 void ContentSuggestionsService::Shutdown() {
36 ntp_snippets_service_ = nullptr; 43 ntp_snippets_service_ = nullptr;
37 suggestions_by_category_.clear(); 44 suggestions_by_category_.clear();
38 providers_by_category_.clear(); 45 providers_by_category_.clear();
39 categories_.clear(); 46 categories_.clear();
40 providers_.clear(); 47 providers_.clear();
41 state_ = State::DISABLED; 48 state_ = State::DISABLED;
42 FOR_EACH_OBSERVER(Observer, observers_, ContentSuggestionsServiceShutdown()); 49 FOR_EACH_OBSERVER(Observer, observers_, ContentSuggestionsServiceShutdown());
43 } 50 }
44 51
52 // static
53 void ContentSuggestionsService::RegisterProfilePrefs(
54 PrefRegistrySimple* registry) {
55 registry->RegisterListPref(prefs::kDismissedCategories);
56 }
57
45 CategoryStatus ContentSuggestionsService::GetCategoryStatus( 58 CategoryStatus ContentSuggestionsService::GetCategoryStatus(
46 Category category) const { 59 Category category) const {
47 if (state_ == State::DISABLED) { 60 if (state_ == State::DISABLED) {
48 return CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED; 61 return CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED;
49 } 62 }
50 63
51 auto iterator = providers_by_category_.find(category); 64 auto iterator = providers_by_category_.find(category);
52 if (iterator == providers_by_category_.end()) 65 if (iterator == providers_by_category_.end())
53 return CategoryStatus::NOT_PROVIDED; 66 return CategoryStatus::NOT_PROVIDED;
54 67
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
143 DCHECK(removed) << "The dismissed suggestion " << suggestion_id 156 DCHECK(removed) << "The dismissed suggestion " << suggestion_id
144 << " has already been removed. Providers must not call" 157 << " has already been removed. Providers must not call"
145 << " OnNewSuggestions in response to DismissSuggestion."; 158 << " OnNewSuggestions in response to DismissSuggestion.";
146 } 159 }
147 160
148 void ContentSuggestionsService::DismissCategory(Category category) { 161 void ContentSuggestionsService::DismissCategory(Category category) {
149 auto providers_it = providers_by_category_.find(category); 162 auto providers_it = providers_by_category_.find(category);
150 if (providers_it == providers_by_category_.end()) 163 if (providers_it == providers_by_category_.end())
151 return; 164 return;
152 165
153 suggestions_by_category_[category].clear(); 166 ContentSuggestionsProvider* provider = providers_it->second;
154 dismissed_providers_by_category_[providers_it->first] = providers_it->second; 167 UnregisterCategory(category, provider);
155 providers_by_category_.erase(providers_it); 168
156 categories_.erase( 169 dismissed_providers_by_category_[category] = provider;
157 std::find(categories_.begin(), categories_.end(), category)); 170 StoreDismissedCategoriesToPrefs();
158 } 171 }
159 172
160 void ContentSuggestionsService::RestoreDismissedCategories() { 173 void ContentSuggestionsService::RestoreDismissedCategories() {
161 // Make a copy as the original will be modified during iteration. 174 // Make a copy as the original will be modified during iteration.
162 auto dismissed_providers_by_category_copy = dismissed_providers_by_category_; 175 auto dismissed_providers_by_category_copy = dismissed_providers_by_category_;
163 for (const auto& category_provider_pair : 176 for (const auto& category_provider_pair :
164 dismissed_providers_by_category_copy) { 177 dismissed_providers_by_category_copy) {
165 RegisterCategoryIfRequired(category_provider_pair.second, 178 RestoreDismissedCategory(category_provider_pair.first);
166 category_provider_pair.first);
167 } 179 }
180 StoreDismissedCategoriesToPrefs();
168 DCHECK(dismissed_providers_by_category_.empty()); 181 DCHECK(dismissed_providers_by_category_.empty());
169 } 182 }
170 183
171 void ContentSuggestionsService::AddObserver(Observer* observer) { 184 void ContentSuggestionsService::AddObserver(Observer* observer) {
172 observers_.AddObserver(observer); 185 observers_.AddObserver(observer);
173 } 186 }
174 187
175 void ContentSuggestionsService::RemoveObserver(Observer* observer) { 188 void ContentSuggestionsService::RemoveObserver(Observer* observer) {
176 observers_.RemoveObserver(observer); 189 observers_.RemoveObserver(observer);
177 } 190 }
178 191
179 void ContentSuggestionsService::RegisterProvider( 192 void ContentSuggestionsService::RegisterProvider(
180 std::unique_ptr<ContentSuggestionsProvider> provider) { 193 std::unique_ptr<ContentSuggestionsProvider> provider) {
181 DCHECK(state_ == State::ENABLED); 194 DCHECK(state_ == State::ENABLED);
182 providers_.push_back(std::move(provider)); 195 providers_.push_back(std::move(provider));
183 } 196 }
184 197
185 //////////////////////////////////////////////////////////////////////////////// 198 ////////////////////////////////////////////////////////////////////////////////
186 // Private methods 199 // Private methods
187 200
188 void ContentSuggestionsService::OnNewSuggestions( 201 void ContentSuggestionsService::OnNewSuggestions(
189 ContentSuggestionsProvider* provider, 202 ContentSuggestionsProvider* provider,
190 Category category, 203 Category category,
191 std::vector<ContentSuggestion> suggestions) { 204 std::vector<ContentSuggestion> suggestions) {
192 if (RegisterCategoryIfRequired(provider, category)) 205 if (TryRegisterProviderForCategory(provider, category)) {
193 NotifyCategoryStatusChanged(category); 206 NotifyCategoryStatusChanged(category);
207 } else if (IsCategoryDismissed(category)) {
208 // The category has been registered as a dismissed one. We need to
209 // check if the dismissal can be cleared now that we received new data.
210 if (suggestions.empty())
211 return;
212
213 RestoreDismissedCategory(category);
214 StoreDismissedCategoriesToPrefs();
215
216 NotifyCategoryStatusChanged(category);
217 }
194 218
195 if (!IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) { 219 if (!IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) {
196 // A provider shouldn't send us suggestions while it's not available. 220 // A provider shouldn't send us suggestions while it's not available.
197 DCHECK(suggestions.empty()); 221 DCHECK(suggestions.empty());
198 return; 222 return;
199 } 223 }
200 224
201 suggestions_by_category_[category] = std::move(suggestions); 225 suggestions_by_category_[category] = std::move(suggestions);
202 226
203 // The positioning of the bookmarks category depends on whether it's empty. 227 // The positioning of the bookmarks category depends on whether it's empty.
204 // TODO(treib): Remove this temporary hack, crbug.com/640568. 228 // TODO(treib): Remove this temporary hack, crbug.com/640568.
205 if (category.IsKnownCategory(KnownCategories::BOOKMARKS)) 229 if (category.IsKnownCategory(KnownCategories::BOOKMARKS))
206 SortCategories(); 230 SortCategories();
207 231
208 FOR_EACH_OBSERVER(Observer, observers_, OnNewSuggestions(category)); 232 FOR_EACH_OBSERVER(Observer, observers_, OnNewSuggestions(category));
209 } 233 }
210 234
211 void ContentSuggestionsService::OnCategoryStatusChanged( 235 void ContentSuggestionsService::OnCategoryStatusChanged(
212 ContentSuggestionsProvider* provider, 236 ContentSuggestionsProvider* provider,
213 Category category, 237 Category category,
214 CategoryStatus new_status) { 238 CategoryStatus new_status) {
215 if (!IsCategoryStatusAvailable(new_status)) {
216 suggestions_by_category_.erase(category);
217 }
218 if (new_status == CategoryStatus::NOT_PROVIDED) { 239 if (new_status == CategoryStatus::NOT_PROVIDED) {
219 DCHECK(providers_by_category_.find(category) != 240 UnregisterCategory(category, provider);
220 providers_by_category_.end());
221 DCHECK_EQ(provider, providers_by_category_.find(category)->second);
222 DismissCategory(category);
223 } else { 241 } else {
224 RegisterCategoryIfRequired(provider, category); 242 if (!IsCategoryStatusAvailable(new_status))
243 suggestions_by_category_.erase(category);
244 TryRegisterProviderForCategory(provider, category);
225 DCHECK_EQ(new_status, provider->GetCategoryStatus(category)); 245 DCHECK_EQ(new_status, provider->GetCategoryStatus(category));
226 } 246 }
227 NotifyCategoryStatusChanged(category); 247
248 if (!IsCategoryDismissed(category))
249 NotifyCategoryStatusChanged(category);
228 } 250 }
229 251
230 void ContentSuggestionsService::OnSuggestionInvalidated( 252 void ContentSuggestionsService::OnSuggestionInvalidated(
231 ContentSuggestionsProvider* provider, 253 ContentSuggestionsProvider* provider,
232 const ContentSuggestion::ID& suggestion_id) { 254 const ContentSuggestion::ID& suggestion_id) {
233 RemoveSuggestionByID(suggestion_id); 255 RemoveSuggestionByID(suggestion_id);
234 FOR_EACH_OBSERVER(Observer, observers_, 256 FOR_EACH_OBSERVER(Observer, observers_,
235 OnSuggestionInvalidated(suggestion_id)); 257 OnSuggestionInvalidated(suggestion_id));
236 } 258 }
237 259
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
274 deleted_urls); 296 deleted_urls);
275 ClearHistory(begin, end, filter); 297 ClearHistory(begin, end, filter);
276 } 298 }
277 } 299 }
278 300
279 void ContentSuggestionsService::HistoryServiceBeingDeleted( 301 void ContentSuggestionsService::HistoryServiceBeingDeleted(
280 history::HistoryService* history_service) { 302 history::HistoryService* history_service) {
281 history_service_observer_.RemoveAll(); 303 history_service_observer_.RemoveAll();
282 } 304 }
283 305
284 bool ContentSuggestionsService::RegisterCategoryIfRequired( 306 bool ContentSuggestionsService::TryRegisterProviderForCategory(
285 ContentSuggestionsProvider* provider, 307 ContentSuggestionsProvider* provider,
286 Category category) { 308 Category category) {
287 auto it = providers_by_category_.find(category); 309 auto it = providers_by_category_.find(category);
288 if (it != providers_by_category_.end()) { 310 if (it != providers_by_category_.end()) {
289 DCHECK_EQ(it->second, provider); 311 DCHECK_EQ(it->second, provider);
290 return false; 312 return false;
291 } 313 }
292 314
293 auto dismissed_it = dismissed_providers_by_category_.find(category); 315 auto dismissed_it = dismissed_providers_by_category_.find(category);
294 if (dismissed_it != dismissed_providers_by_category_.end()) { 316 if (dismissed_it != dismissed_providers_by_category_.end()) {
295 DCHECK_EQ(dismissed_it->second, provider); 317 // The initialisation of dismissed categories registers them with |nullptr|
296 dismissed_providers_by_category_.erase(dismissed_it); 318 // for providers, we need to check for that to see if the provider is
319 // already registered or not.
320 if (!dismissed_it->second) {
321 dismissed_it->second = provider;
322 } else {
323 DCHECK_EQ(dismissed_it->second, provider);
324 }
325 return false;
297 } 326 }
298 327
328 RegisterCategory(category, provider);
329 return true;
330 }
331
332 void ContentSuggestionsService::RegisterCategory(
333 Category category,
334 ContentSuggestionsProvider* provider) {
335 DCHECK(!base::ContainsKey(providers_by_category_, category));
336 DCHECK(!IsCategoryDismissed(category));
337
299 providers_by_category_[category] = provider; 338 providers_by_category_[category] = provider;
300 categories_.push_back(category); 339 categories_.push_back(category);
301 SortCategories(); 340 SortCategories();
302 if (IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) { 341 if (IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) {
303 suggestions_by_category_.insert( 342 suggestions_by_category_.insert(
304 std::make_pair(category, std::vector<ContentSuggestion>())); 343 std::make_pair(category, std::vector<ContentSuggestion>()));
305 } 344 }
306 return true; 345 }
346
347 void ContentSuggestionsService::UnregisterCategory(
348 Category category,
349 ContentSuggestionsProvider* provider) {
350 auto providers_it = providers_by_category_.find(category);
351 if (providers_it == providers_by_category_.end()) {
352 DCHECK(IsCategoryDismissed(category));
353 return;
354 }
355
356 DCHECK_EQ(provider, providers_it->second);
357 providers_by_category_.erase(providers_it);
358 categories_.erase(
359 std::find(categories_.begin(), categories_.end(), category));
360 suggestions_by_category_.erase(category);
307 } 361 }
308 362
309 bool ContentSuggestionsService::RemoveSuggestionByID( 363 bool ContentSuggestionsService::RemoveSuggestionByID(
310 const ContentSuggestion::ID& suggestion_id) { 364 const ContentSuggestion::ID& suggestion_id) {
311 std::vector<ContentSuggestion>* suggestions = 365 std::vector<ContentSuggestion>* suggestions =
312 &suggestions_by_category_[suggestion_id.category()]; 366 &suggestions_by_category_[suggestion_id.category()];
313 auto position = 367 auto position =
314 std::find_if(suggestions->begin(), suggestions->end(), 368 std::find_if(suggestions->begin(), suggestions->end(),
315 [&suggestion_id](const ContentSuggestion& suggestion) { 369 [&suggestion_id](const ContentSuggestion& suggestion) {
316 return suggestion_id == suggestion.id(); 370 return suggestion_id == suggestion.id();
(...skipping 29 matching lines...) Expand all
346 if (bookmarks_empty) { 400 if (bookmarks_empty) {
347 if (left.IsKnownCategory(KnownCategories::BOOKMARKS)) 401 if (left.IsKnownCategory(KnownCategories::BOOKMARKS))
348 return false; 402 return false;
349 if (right.IsKnownCategory(KnownCategories::BOOKMARKS)) 403 if (right.IsKnownCategory(KnownCategories::BOOKMARKS))
350 return true; 404 return true;
351 } 405 }
352 return category_factory_.CompareCategories(left, right); 406 return category_factory_.CompareCategories(left, right);
353 }); 407 });
354 } 408 }
355 409
410 bool ContentSuggestionsService::IsCategoryDismissed(Category category) const {
411 return base::ContainsKey(dismissed_providers_by_category_, category);
412 }
413
414 void ContentSuggestionsService::RestoreDismissedCategory(Category category) {
415 auto dismissed_it = dismissed_providers_by_category_.find(category);
416 DCHECK(base::ContainsKey(dismissed_providers_by_category_, category));
417
418 // Keep the reference to the provider and remove it from the dismissed ones,
419 // because the category registration enforces that it's not dismissed.
420 ContentSuggestionsProvider* provider = dismissed_it->second;
421 dismissed_providers_by_category_.erase(dismissed_it);
422
423 if (provider)
424 RegisterCategory(category, provider);
425 }
426
427 void ContentSuggestionsService::RestoreDismissedCategoriesFromPrefs() {
428 // This must only be called at startup.
429 DCHECK(dismissed_providers_by_category_.empty());
430 DCHECK(providers_by_category_.empty());
431
432 const base::ListValue* list =
433 pref_service_->GetList(prefs::kDismissedCategories);
434 for (const std::unique_ptr<base::Value>& entry : *list) {
435 int id = 0;
436 if (!entry->GetAsInteger(&id)) {
437 DLOG(WARNING) << "Invalid category pref value: " << *entry;
438 continue;
439 }
440
441 // When the provider is registered, it will be stored in this map.
442 dismissed_providers_by_category_[category_factory()->FromIDValue(id)] =
443 nullptr;
444 }
445 }
446
447 void ContentSuggestionsService::StoreDismissedCategoriesToPrefs() {
448 base::ListValue list;
449 for (const auto& category_provider_pair : dismissed_providers_by_category_) {
450 list.AppendInteger(category_provider_pair.first.id());
451 }
452
453 pref_service_->Set(prefs::kDismissedCategories, list);
454 }
455
356 } // namespace ntp_snippets 456 } // namespace ntp_snippets
OLDNEW
« no previous file with comments | « components/ntp_snippets/content_suggestions_service.h ('k') | components/ntp_snippets/content_suggestions_service_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698