blob: 3e8e7f4126877ca965e254b276239f50df322b52 [file] [log] [blame]
[email protected]c764b2822013-07-03 04:16:481// 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#include "chrome/browser/android/most_visited_sites.h"
6
dchengbc6125d2015-12-30 01:54:077#include <utility>
8
[email protected]c764b2822013-07-03 04:16:489#include "base/android/jni_android.h"
10#include "base/android/jni_array.h"
11#include "base/android/jni_string.h"
12#include "base/android/scoped_java_ref.h"
[email protected]d33adb40d2014-06-03 21:38:4613#include "base/callback.h"
treibcffa6502015-08-06 09:12:2714#include "base/command_line.h"
zea9c7707d2015-11-03 21:41:1415#include "base/metrics/field_trial.h"
[email protected]9ed2f5c2014-06-13 14:55:2416#include "base/metrics/histogram.h"
[email protected]1d778e12014-06-17 02:28:5117#include "base/metrics/sparse_histogram.h"
[email protected]9ed2f5c2014-06-13 14:55:2418#include "base/strings/string_number_conversions.h"
treibeba6cee2015-09-09 13:25:5919#include "base/strings/string_util.h"
[email protected]9ed2f5c2014-06-13 14:55:2420#include "base/strings/stringprintf.h"
[email protected]bc093e82014-03-04 21:49:1121#include "base/strings/utf_string_conversions.h"
[email protected]6d21c702014-05-15 06:10:2122#include "base/time/time.h"
treibcffa6502015-08-06 09:12:2723#include "chrome/browser/android/popular_sites.h"
jitendra.ks30f03392015-01-28 09:47:1824#include "chrome/browser/history/top_sites_factory.h"
[email protected]c764b2822013-07-03 04:16:4825#include "chrome/browser/profiles/profile.h"
26#include "chrome/browser/profiles/profile_android.h"
[email protected]bc093e82014-03-04 21:49:1127#include "chrome/browser/search/suggestions/suggestions_service_factory.h"
[email protected]81e09e72014-05-07 13:41:2228#include "chrome/browser/search/suggestions/suggestions_source.h"
treibb75f636a2016-02-12 10:21:1229#include "chrome/browser/search/suggestions/suggestions_utils.h"
atanasova9572aaf2016-02-26 18:08:2630#include "chrome/browser/supervised_user/supervised_user_service.h"
31#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
[email protected]1d1ab152014-08-21 17:14:1232#include "chrome/browser/sync/profile_sync_service_factory.h"
[email protected]81e09e72014-05-07 13:41:2233#include "chrome/browser/thumbnails/thumbnail_list_source.h"
treibcffa6502015-08-06 09:12:2734#include "chrome/common/chrome_switches.h"
knncbbd754d2015-09-09 15:43:4035#include "chrome/common/pref_names.h"
blundell7282b512015-11-09 07:21:1136#include "components/browser_sync/browser/profile_sync_service.h"
sdefresne0da3bc02015-01-29 18:26:3537#include "components/history/core/browser/top_sites.h"
knncbbd754d2015-09-09 15:43:4038#include "components/pref_registry/pref_registry_syncable.h"
brettwb1fc1b82016-02-02 00:19:0839#include "components/prefs/pref_service.h"
[email protected]bdceb3ba2014-07-25 16:47:4840#include "components/suggestions/suggestions_service.h"
zea9c7707d2015-11-03 21:41:1441#include "components/variations/variations_associated_data.h"
[email protected]c764b2822013-07-03 04:16:4842#include "content/public/browser/browser_thread.h"
[email protected]81e09e72014-05-07 13:41:2243#include "content/public/browser/url_data_source.h"
[email protected]c764b2822013-07-03 04:16:4844#include "jni/MostVisitedSites_jni.h"
45#include "third_party/skia/include/core/SkBitmap.h"
46#include "ui/gfx/android/java_bitmap.h"
47#include "ui/gfx/codec/jpeg_codec.h"
treibcffa6502015-08-06 09:12:2748#include "url/gurl.h"
[email protected]c764b2822013-07-03 04:16:4849
50using base::android::AttachCurrentThread;
[email protected]c764b2822013-07-03 04:16:4851using base::android::ConvertJavaStringToUTF8;
52using base::android::ScopedJavaGlobalRef;
knn6cf777fc2015-05-06 13:21:4853using base::android::ScopedJavaLocalRef;
[email protected]c764b2822013-07-03 04:16:4854using base::android::ToJavaArrayOfStrings;
[email protected]c764b2822013-07-03 04:16:4855using content::BrowserThread;
56using history::TopSites;
[email protected]bc093e82014-03-04 21:49:1157using suggestions::ChromeSuggestion;
58using suggestions::SuggestionsProfile;
59using suggestions::SuggestionsService;
60using suggestions::SuggestionsServiceFactory;
[email protected]c764b2822013-07-03 04:16:4861
[email protected]c764b2822013-07-03 04:16:4862namespace {
63
treib783a3372015-08-25 16:24:3964// Identifiers for the various tile sources.
65const char kHistogramClientName[] = "client";
66const char kHistogramServerName[] = "server";
67const char kHistogramServerFormat[] = "server%d";
68const char kHistogramPopularName[] = "popular";
atanasova9572aaf2016-02-26 18:08:2669const char kHistogramWhitelistName[] = "whitelist";
[email protected]9ed2f5c2014-06-13 14:55:2470
treib508939a2015-08-25 10:07:5671const char kPopularSitesFieldTrialName[] = "NTPPopularSites";
72
newt2a47ce72015-10-01 20:09:4973// The visual type of a most visited tile.
74//
75// These values must stay in sync with the MostVisitedTileType enum
76// in histograms.xml.
77//
78// A Java counterpart will be generated for this enum.
79// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.ntp
80enum MostVisitedTileType {
81 // The icon or thumbnail hasn't loaded yet.
82 NONE,
83 // The item displays a site's actual favicon or touch icon.
84 ICON_REAL,
85 // The item displays a color derived from the site's favicon or touch icon.
86 ICON_COLOR,
87 // The item displays a default gray box in place of an icon.
88 ICON_DEFAULT,
newt2a47ce72015-10-01 20:09:4989 NUM_TILE_TYPES,
90};
91
knn6cf777fc2015-05-06 13:21:4892scoped_ptr<SkBitmap> MaybeFetchLocalThumbnail(
93 const GURL& url,
94 const scoped_refptr<TopSites>& top_sites) {
95 DCHECK_CURRENTLY_ON(BrowserThread::DB);
96 scoped_refptr<base::RefCountedMemory> image;
97 scoped_ptr<SkBitmap> bitmap;
98 if (top_sites && top_sites->GetPageThumbnail(url, false, &image))
99 bitmap.reset(gfx::JPEGCodec::Decode(image->front(), image->size()));
dchengbc6125d2015-12-30 01:54:07100 return bitmap;
[email protected]d33adb40d2014-06-03 21:38:46101}
102
[email protected]92a598822014-06-17 10:25:56103// Log an event for a given |histogram| at a given element |position|. This
104// routine exists because regular histogram macros are cached thus can't be used
105// if the name of the histogram will change at a given call site.
treib783a3372015-08-25 16:24:39106void LogHistogramEvent(const std::string& histogram,
107 int position,
[email protected]92a598822014-06-17 10:25:56108 int num_sites) {
[email protected]9ed2f5c2014-06-13 14:55:24109 base::HistogramBase* counter = base::LinearHistogram::FactoryGet(
[email protected]92a598822014-06-17 10:25:56110 histogram,
[email protected]9ed2f5c2014-06-13 14:55:24111 1,
112 num_sites,
113 num_sites + 1,
114 base::Histogram::kUmaTargetedHistogramFlag);
[email protected]6f674482014-08-11 22:50:49115 if (counter)
116 counter->Add(position);
[email protected]9ed2f5c2014-06-13 14:55:24117}
118
treibcffa6502015-08-06 09:12:27119bool ShouldShowPopularSites() {
120 // Note: It's important to query the field trial state first, to ensure that
121 // UMA reports the correct group.
122 const std::string group_name =
treib508939a2015-08-25 10:07:56123 base::FieldTrialList::FindFullName(kPopularSitesFieldTrialName);
treibcffa6502015-08-06 09:12:27124 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
125 if (cmd_line->HasSwitch(switches::kDisableNTPPopularSites))
126 return false;
127 if (cmd_line->HasSwitch(switches::kEnableNTPPopularSites))
128 return true;
treibeba6cee2015-09-09 13:25:59129 return base::StartsWith(group_name, "Enabled",
130 base::CompareCase::INSENSITIVE_ASCII);
treibcffa6502015-08-06 09:12:27131}
132
treibb28d6502015-09-22 14:28:50133std::string GetPopularSitesCountry() {
134 return variations::GetVariationParamValue(kPopularSitesFieldTrialName,
135 "country");
136}
137
138std::string GetPopularSitesVersion() {
139 return variations::GetVariationParamValue(kPopularSitesFieldTrialName,
140 "version");
141}
142
treibf79ed40d2015-09-22 12:38:06143// Determine whether we need any popular suggestions to fill up a grid of
144// |num_tiles| tiles.
145bool NeedPopularSites(const PrefService* prefs, size_t num_tiles) {
146 const base::ListValue* source_list =
147 prefs->GetList(prefs::kNTPSuggestionsIsPersonal);
148 // If there aren't enough previous suggestions to fill the grid, we need
149 // popular suggestions.
150 if (source_list->GetSize() < num_tiles)
151 return true;
152 // Otherwise, if any of the previous suggestions is not personal, then also
153 // get popular suggestions.
154 for (size_t i = 0; i < num_tiles; ++i) {
155 bool is_personal = false;
156 if (source_list->GetBoolean(i, &is_personal) && !is_personal)
157 return true;
158 }
159 // The whole grid is already filled with personal suggestions, no point in
160 // bothering with popular ones.
161 return false;
162}
163
[email protected]c764b2822013-07-03 04:16:48164} // namespace
165
knncbbd754d2015-09-09 15:43:40166MostVisitedSites::Suggestion::Suggestion(const base::string16& title,
167 const std::string& url,
168 MostVisitedSource source)
169 : title(title), url(url), source(source), provider_index(-1) {}
170
171MostVisitedSites::Suggestion::Suggestion(const base::string16& title,
172 const GURL& url,
173 MostVisitedSource source)
174 : title(title), url(url), source(source), provider_index(-1) {}
175
176MostVisitedSites::Suggestion::Suggestion(const base::string16& title,
177 const std::string& url,
178 MostVisitedSource source,
179 int provider_index)
180 : title(title), url(url), source(source), provider_index(provider_index) {
181 DCHECK_EQ(MostVisitedSites::SUGGESTIONS_SERVICE, source);
182}
183
184MostVisitedSites::Suggestion::~Suggestion() {}
185
186std::string MostVisitedSites::Suggestion::GetSourceHistogramName() const {
187 switch (source) {
188 case MostVisitedSites::TOP_SITES:
189 return kHistogramClientName;
190 case MostVisitedSites::POPULAR:
191 return kHistogramPopularName;
atanasova9572aaf2016-02-26 18:08:26192 case MostVisitedSites::WHITELIST:
193 return kHistogramWhitelistName;
knncbbd754d2015-09-09 15:43:40194 case MostVisitedSites::SUGGESTIONS_SERVICE:
195 return provider_index >= 0
196 ? base::StringPrintf(kHistogramServerFormat, provider_index)
197 : kHistogramServerName;
198 }
199 NOTREACHED();
200 return std::string();
201}
202
[email protected]7ceb4e32013-12-06 04:13:04203MostVisitedSites::MostVisitedSites(Profile* profile)
treib783a3372015-08-25 16:24:39204 : profile_(profile), num_sites_(0), received_most_visited_sites_(false),
205 received_popular_sites_(false), recorded_uma_(false),
mathpd91e8eb2015-03-24 17:37:47206 scoped_observer_(this), weak_ptr_factory_(this) {
[email protected]81e09e72014-05-07 13:41:22207 // Register the debugging page for the Suggestions Service and the thumbnails
208 // debugging page.
209 content::URLDataSource::Add(profile_,
210 new suggestions::SuggestionsSource(profile_));
211 content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_));
[email protected]1d1ab152014-08-21 17:14:12212
213 // Register this class as an observer to the sync service. It is important to
214 // be notified of changes in the sync state such as initialization, sync
215 // being enabled or disabled, etc.
216 ProfileSyncService* profile_sync_service =
217 ProfileSyncServiceFactory::GetForProfile(profile_);
atanasova9572aaf2016-02-26 18:08:26218 profile_sync_service->AddObserver(this);
219
220 SupervisedUserService* supervised_user_service =
221 SupervisedUserServiceFactory::GetForProfile(profile_);
222 supervised_user_service->AddObserver(this);
[email protected]7ceb4e32013-12-06 04:13:04223}
[email protected]c764b2822013-07-03 04:16:48224
[email protected]7ceb4e32013-12-06 04:13:04225MostVisitedSites::~MostVisitedSites() {
feng94a42102014-08-26 23:42:39226 ProfileSyncService* profile_sync_service =
227 ProfileSyncServiceFactory::GetForProfile(profile_);
atanasova9572aaf2016-02-26 18:08:26228 profile_sync_service->RemoveObserver(this);
229
230 SupervisedUserService* supervised_user_service =
231 SupervisedUserServiceFactory::GetForProfile(profile_);
232 supervised_user_service->RemoveObserver(this);
[email protected]7ceb4e32013-12-06 04:13:04233}
[email protected]c764b2822013-07-03 04:16:48234
tornee621a272015-11-30 18:40:53235void MostVisitedSites::Destroy(JNIEnv* env, const JavaParamRef<jobject>& obj) {
[email protected]7ceb4e32013-12-06 04:13:04236 delete this;
237}
[email protected]c764b2822013-07-03 04:16:48238
tornee621a272015-11-30 18:40:53239void MostVisitedSites::SetMostVisitedURLsObserver(
240 JNIEnv* env,
241 const JavaParamRef<jobject>& obj,
242 const JavaParamRef<jobject>& j_observer,
243 jint num_sites) {
[email protected]7ceb4e32013-12-06 04:13:04244 observer_.Reset(env, j_observer);
245 num_sites_ = num_sites;
[email protected]852fc792013-10-04 04:15:12246
treibf79ed40d2015-09-22 12:38:06247 if (ShouldShowPopularSites() &&
248 NeedPopularSites(profile_->GetPrefs(), num_sites_)) {
249 popular_sites_.reset(new PopularSites(
250 profile_,
treibb28d6502015-09-22 14:28:50251 GetPopularSitesCountry(),
252 GetPopularSitesVersion(),
treibd73c9ad2015-09-22 16:20:48253 false,
treibf79ed40d2015-09-22 12:38:06254 base::Bind(&MostVisitedSites::OnPopularSitesAvailable,
255 base::Unretained(this))));
256 } else {
257 received_popular_sites_ = true;
258 }
259
[email protected]7ceb4e32013-12-06 04:13:04260 QueryMostVisitedURLs();
261
jitendra.ks30f03392015-01-28 09:47:18262 scoped_refptr<history::TopSites> top_sites =
263 TopSitesFactory::GetForProfile(profile_);
[email protected]7ceb4e32013-12-06 04:13:04264 if (top_sites) {
265 // TopSites updates itself after a delay. To ensure up-to-date results,
266 // force an update now.
267 top_sites->SyncWithHistory();
268
sdefresneedf9e01f2015-01-13 19:45:41269 // Register as TopSitesObserver so that we can update ourselves when the
270 // TopSites changes.
jitendra.ks30f03392015-01-28 09:47:18271 scoped_observer_.Add(top_sites.get());
[email protected]7ceb4e32013-12-06 04:13:04272 }
[email protected]c764b2822013-07-03 04:16:48273}
274
tornee621a272015-11-30 18:40:53275void MostVisitedSites::GetURLThumbnail(
276 JNIEnv* env,
277 const JavaParamRef<jobject>& obj,
278 const JavaParamRef<jstring>& j_url,
279 const JavaParamRef<jobject>& j_callback_obj) {
mostynb351bf982015-03-25 22:26:58280 DCHECK_CURRENTLY_ON(BrowserThread::UI);
knn6cf777fc2015-05-06 13:21:48281 scoped_ptr<ScopedJavaGlobalRef<jobject>> j_callback(
282 new ScopedJavaGlobalRef<jobject>());
[email protected]7ceb4e32013-12-06 04:13:04283 j_callback->Reset(env, j_callback_obj);
[email protected]c764b2822013-07-03 04:16:48284
knn6cf777fc2015-05-06 13:21:48285 GURL url(ConvertJavaStringToUTF8(env, j_url));
jitendra.ks30f03392015-01-28 09:47:18286 scoped_refptr<TopSites> top_sites(TopSitesFactory::GetForProfile(profile_));
[email protected]d33adb40d2014-06-03 21:38:46287
knn6cf777fc2015-05-06 13:21:48288 BrowserThread::PostTaskAndReplyWithResult(
[email protected]d33adb40d2014-06-03 21:38:46289 BrowserThread::DB, FROM_HERE,
knn6cf777fc2015-05-06 13:21:48290 base::Bind(&MaybeFetchLocalThumbnail, url, top_sites),
291 base::Bind(&MostVisitedSites::OnLocalThumbnailFetched,
292 weak_ptr_factory_.GetWeakPtr(), url,
293 base::Passed(&j_callback)));
294}
295
296void MostVisitedSites::OnLocalThumbnailFetched(
297 const GURL& url,
298 scoped_ptr<ScopedJavaGlobalRef<jobject>> j_callback,
299 scoped_ptr<SkBitmap> bitmap) {
300 DCHECK_CURRENTLY_ON(BrowserThread::UI);
301 if (!bitmap.get()) {
302 // A thumbnail is not locally available for |url|. Make sure it is put in
303 // the list to be fetched at the next visit to this site.
304 scoped_refptr<TopSites> top_sites(TopSitesFactory::GetForProfile(profile_));
305 if (top_sites)
306 top_sites->AddForcedURL(url, base::Time::Now());
treibac5372f2015-09-09 09:08:22307 // Also fetch a remote thumbnail if possible. PopularSites or the
308 // SuggestionsService can supply a thumbnail download URL.
knn6cf777fc2015-05-06 13:21:48309 SuggestionsService* suggestions_service =
treibac5372f2015-09-09 09:08:22310 SuggestionsServiceFactory::GetForProfile(profile_);
knn6cf777fc2015-05-06 13:21:48311 if (suggestions_service) {
treibac5372f2015-09-09 09:08:22312 if (popular_sites_) {
313 const std::vector<PopularSites::Site>& sites = popular_sites_->sites();
314 auto it = std::find_if(sites.begin(), sites.end(),
315 [&url](const PopularSites::Site& site) {
316 return site.url == url;
317 });
318 if (it != sites.end() && it->thumbnail_url.is_valid()) {
319 return suggestions_service->GetPageThumbnailWithURL(
320 url, it->thumbnail_url,
321 base::Bind(&MostVisitedSites::OnObtainedThumbnail,
322 weak_ptr_factory_.GetWeakPtr(), false,
323 base::Passed(&j_callback)));
324 }
325 }
326 if (mv_source_ == SUGGESTIONS_SERVICE) {
327 return suggestions_service->GetPageThumbnail(
328 url, base::Bind(&MostVisitedSites::OnObtainedThumbnail,
329 weak_ptr_factory_.GetWeakPtr(), false,
330 base::Passed(&j_callback)));
331 }
knn6cf777fc2015-05-06 13:21:48332 }
333 }
dchengbc6125d2015-12-30 01:54:07334 OnObtainedThumbnail(true, std::move(j_callback), url, bitmap.get());
knn6cf777fc2015-05-06 13:21:48335}
336
337void MostVisitedSites::OnObtainedThumbnail(
338 bool is_local_thumbnail,
339 scoped_ptr<ScopedJavaGlobalRef<jobject>> j_callback,
340 const GURL& url,
341 const SkBitmap* bitmap) {
342 DCHECK_CURRENTLY_ON(BrowserThread::UI);
343 JNIEnv* env = AttachCurrentThread();
344 ScopedJavaLocalRef<jobject> j_bitmap;
newt2a47ce72015-10-01 20:09:49345 if (bitmap)
knn6cf777fc2015-05-06 13:21:48346 j_bitmap = gfx::ConvertToJavaBitmap(bitmap);
knn6cf777fc2015-05-06 13:21:48347 Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
newt2a47ce72015-10-01 20:09:49348 env, j_callback->obj(), j_bitmap.obj(), is_local_thumbnail);
[email protected]c764b2822013-07-03 04:16:48349}
[email protected]a4c2f282013-07-20 05:26:05350
newt4f36ac0b2016-01-21 02:49:25351void MostVisitedSites::AddOrRemoveBlacklistedUrl(
352 JNIEnv* env,
353 const JavaParamRef<jobject>& obj,
354 const JavaParamRef<jstring>& j_url,
355 jboolean add_url) {
knn6cf777fc2015-05-06 13:21:48356 GURL url(ConvertJavaStringToUTF8(env, j_url));
[email protected]a4c2f282013-07-20 05:26:05357
treibfac75192015-08-17 13:21:24358 // Always blacklist in the local TopSites.
359 scoped_refptr<TopSites> top_sites = TopSitesFactory::GetForProfile(profile_);
newt4f36ac0b2016-01-21 02:49:25360 if (top_sites) {
361 if (add_url) {
362 top_sites->AddBlacklistedURL(url);
363 } else {
364 top_sites->RemoveBlacklistedURL(url);
365 }
366 }
[email protected]d4975242014-06-02 18:16:20367
treibfac75192015-08-17 13:21:24368 // Only blacklist in the server-side suggestions service if it's active.
369 if (mv_source_ == SUGGESTIONS_SERVICE) {
370 SuggestionsService* suggestions_service =
371 SuggestionsServiceFactory::GetForProfile(profile_);
372 DCHECK(suggestions_service);
newt4f36ac0b2016-01-21 02:49:25373 suggestions::SuggestionsService::ResponseCallback callback(
374 base::Bind(&MostVisitedSites::OnSuggestionsProfileAvailable,
375 weak_ptr_factory_.GetWeakPtr()));
376 if (add_url) {
377 suggestions_service->BlacklistURL(url, callback, base::Closure());
378 } else {
379 suggestions_service->UndoBlacklistURL(url, callback, base::Closure());
380 }
[email protected]d4975242014-06-02 18:16:20381 }
[email protected]a4c2f282013-07-20 05:26:05382}
[email protected]7ceb4e32013-12-06 04:13:04383
tornee621a272015-11-30 18:40:53384void MostVisitedSites::RecordTileTypeMetrics(
385 JNIEnv* env,
386 const JavaParamRef<jobject>& obj,
Newton Allenea5f0c02015-12-04 03:02:41387 const JavaParamRef<jintArray>& jtile_types) {
newt2a47ce72015-10-01 20:09:49388 std::vector<int> tile_types;
389 base::android::JavaIntArrayToIntVector(env, jtile_types, &tile_types);
390 DCHECK_EQ(current_suggestions_.size(), tile_types.size());
391
392 int counts_per_type[NUM_TILE_TYPES] = {0};
393 for (size_t i = 0; i < tile_types.size(); ++i) {
394 int tile_type = tile_types[i];
395 ++counts_per_type[tile_type];
396 std::string histogram = base::StringPrintf(
397 "NewTabPage.TileType.%s",
398 current_suggestions_[i]->GetSourceHistogramName().c_str());
399 LogHistogramEvent(histogram, tile_type, NUM_TILE_TYPES);
400 }
401
Newton Allenea5f0c02015-12-04 03:02:41402 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.IconsReal",
403 counts_per_type[ICON_REAL]);
404 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.IconsColor",
405 counts_per_type[ICON_COLOR]);
406 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.IconsGray",
407 counts_per_type[ICON_DEFAULT]);
newt2a47ce72015-10-01 20:09:49408}
409
tornee621a272015-11-30 18:40:53410void MostVisitedSites::RecordOpenedMostVisitedItem(
411 JNIEnv* env,
412 const JavaParamRef<jobject>& obj,
413 jint index,
414 jint tile_type) {
treib783a3372015-08-25 16:24:39415 DCHECK_GE(index, 0);
knncbbd754d2015-09-09 15:43:40416 DCHECK_LT(index, static_cast<int>(current_suggestions_.size()));
417 std::string histogram = base::StringPrintf(
newt2a47ce72015-10-01 20:09:49418 "NewTabPage.MostVisited.%s",
knncbbd754d2015-09-09 15:43:40419 current_suggestions_[index]->GetSourceHistogramName().c_str());
treib783a3372015-08-25 16:24:39420 LogHistogramEvent(histogram, index, num_sites_);
newt2a47ce72015-10-01 20:09:49421
422 histogram = base::StringPrintf(
423 "NewTabPage.TileTypeClicked.%s",
424 current_suggestions_[index]->GetSourceHistogramName().c_str());
425 LogHistogramEvent(histogram, tile_type, NUM_TILE_TYPES);
[email protected]92a598822014-06-17 10:25:56426}
427
[email protected]1d1ab152014-08-21 17:14:12428void MostVisitedSites::OnStateChanged() {
429 // There have been changes to the sync state. This class cares about a few
430 // (just initialized, enabled/disabled or history sync state changed). Re-run
431 // the query code which will use the proper state.
432 QueryMostVisitedURLs();
433}
434
atanasova9572aaf2016-02-26 18:08:26435void MostVisitedSites::OnURLFilterChanged() {
436 QueryMostVisitedURLs();
437}
438
[email protected]7ceb4e32013-12-06 04:13:04439// static
440bool MostVisitedSites::Register(JNIEnv* env) {
441 return RegisterNativesImpl(env);
442}
443
knncbbd754d2015-09-09 15:43:40444// static
445void MostVisitedSites::RegisterProfilePrefs(
446 user_prefs::PrefRegistrySyncable* registry) {
447 registry->RegisterListPref(prefs::kNTPSuggestionsURL);
448 registry->RegisterListPref(prefs::kNTPSuggestionsIsPersonal);
449}
450
[email protected]7ceb4e32013-12-06 04:13:04451void MostVisitedSites::QueryMostVisitedURLs() {
[email protected]bc093e82014-03-04 21:49:11452 SuggestionsService* suggestions_service =
[email protected]d4975242014-06-02 18:16:20453 SuggestionsServiceFactory::GetForProfile(profile_);
[email protected]bc093e82014-03-04 21:49:11454 if (suggestions_service) {
455 // Suggestions service is enabled, initiate a query.
456 suggestions_service->FetchSuggestionsData(
treibb75f636a2016-02-12 10:21:12457 suggestions::GetSyncState(profile_),
knn6cf777fc2015-05-06 13:21:48458 base::Bind(&MostVisitedSites::OnSuggestionsProfileAvailable,
459 weak_ptr_factory_.GetWeakPtr()));
[email protected]bc093e82014-03-04 21:49:11460 } else {
461 InitiateTopSitesQuery();
462 }
463}
464
465void MostVisitedSites::InitiateTopSitesQuery() {
jitendra.ks30f03392015-01-28 09:47:18466 scoped_refptr<TopSites> top_sites = TopSitesFactory::GetForProfile(profile_);
[email protected]7ceb4e32013-12-06 04:13:04467 if (!top_sites)
468 return;
469
470 top_sites->GetMostVisitedURLs(
knn6cf777fc2015-05-06 13:21:48471 base::Bind(&MostVisitedSites::OnMostVisitedURLsAvailable,
472 weak_ptr_factory_.GetWeakPtr()),
[email protected]7ceb4e32013-12-06 04:13:04473 false);
474}
475
[email protected]d4975242014-06-02 18:16:20476void MostVisitedSites::OnMostVisitedURLsAvailable(
[email protected]d4975242014-06-02 18:16:20477 const history::MostVisitedURLList& visited_list) {
atanasova9572aaf2016-02-26 18:08:26478 MostVisitedSites::SuggestionsVector suggestions;
knncbbd754d2015-09-09 15:43:40479 size_t num_tiles =
480 std::min(visited_list.size(), static_cast<size_t>(num_sites_));
481 for (size_t i = 0; i < num_tiles; ++i) {
knn6cf777fc2015-05-06 13:21:48482 const history::MostVisitedURL& visited = visited_list[i];
483 if (visited.url.is_empty()) {
484 num_tiles = i;
485 break; // This is the signal that there are no more real visited sites.
486 }
atanasova9572aaf2016-02-26 18:08:26487 suggestions.push_back(make_scoped_ptr(
488 new Suggestion(visited.title, visited.url.spec(), TOP_SITES)));
knn6cf777fc2015-05-06 13:21:48489 }
[email protected]d4975242014-06-02 18:16:20490
treib783a3372015-08-25 16:24:39491 received_most_visited_sites_ = true;
knn6cf777fc2015-05-06 13:21:48492 mv_source_ = TOP_SITES;
atanasova9572aaf2016-02-26 18:08:26493 SaveNewNTPSuggestions(&suggestions);
knncbbd754d2015-09-09 15:43:40494 NotifyMostVisitedURLsObserver();
[email protected]d4975242014-06-02 18:16:20495}
496
[email protected]bc093e82014-03-04 21:49:11497void MostVisitedSites::OnSuggestionsProfileAvailable(
[email protected]bc093e82014-03-04 21:49:11498 const SuggestionsProfile& suggestions_profile) {
knn6cf777fc2015-05-06 13:21:48499 int num_tiles = suggestions_profile.suggestions_size();
mathpd91e8eb2015-03-24 17:37:47500 // With no server suggestions, fall back to local Most Visited.
knn6cf777fc2015-05-06 13:21:48501 if (num_tiles == 0) {
[email protected]bc093e82014-03-04 21:49:11502 InitiateTopSitesQuery();
503 return;
504 }
knn6cf777fc2015-05-06 13:21:48505 if (num_sites_ < num_tiles)
506 num_tiles = num_sites_;
[email protected]bc093e82014-03-04 21:49:11507
atanasova9572aaf2016-02-26 18:08:26508 MostVisitedSites::SuggestionsVector suggestions;
knn6cf777fc2015-05-06 13:21:48509 for (int i = 0; i < num_tiles; ++i) {
[email protected]bc093e82014-03-04 21:49:11510 const ChromeSuggestion& suggestion = suggestions_profile.suggestions(i);
atanasova9572aaf2016-02-26 18:08:26511 suggestions.push_back(make_scoped_ptr(new Suggestion(
knncbbd754d2015-09-09 15:43:40512 base::UTF8ToUTF16(suggestion.title()), suggestion.url(),
513 SUGGESTIONS_SERVICE,
atanasova9572aaf2016-02-26 18:08:26514 suggestion.providers_size() > 0 ? suggestion.providers(0) : -1)));
[email protected]bc093e82014-03-04 21:49:11515 }
treib783a3372015-08-25 16:24:39516
517 received_most_visited_sites_ = true;
[email protected]d4975242014-06-02 18:16:20518 mv_source_ = SUGGESTIONS_SERVICE;
atanasova9572aaf2016-02-26 18:08:26519 SaveNewNTPSuggestions(&suggestions);
knncbbd754d2015-09-09 15:43:40520 NotifyMostVisitedURLsObserver();
knn6cf777fc2015-05-06 13:21:48521}
[email protected]d4975242014-06-02 18:16:20522
atanasova9572aaf2016-02-26 18:08:26523MostVisitedSites::SuggestionsVector
524MostVisitedSites::CreateWhitelistEntryPointSuggestions(
525 const MostVisitedSites::SuggestionsVector& personal_suggestions) {
526 size_t num_personal_suggestions = personal_suggestions.size();
knncbbd754d2015-09-09 15:43:40527 DCHECK_LE(num_personal_suggestions, static_cast<size_t>(num_sites_));
treibcffa6502015-08-06 09:12:27528
atanasova9572aaf2016-02-26 18:08:26529 size_t num_whitelist_suggestions = num_sites_ - num_personal_suggestions;
530 MostVisitedSites::SuggestionsVector whitelist_suggestions;
531
532 SupervisedUserService* supervised_user_service =
533 SupervisedUserServiceFactory::GetForProfile(profile_);
534
535 for (const auto& whitelist : supervised_user_service->whitelists()) {
536 whitelist_suggestions.push_back(make_scoped_ptr(new Suggestion(
537 whitelist->title(), whitelist->entry_point(), WHITELIST)));
538 if (whitelist_suggestions.size() >= num_whitelist_suggestions)
539 break;
540 }
541
542 return whitelist_suggestions;
543}
544
545MostVisitedSites::SuggestionsVector
546MostVisitedSites::CreatePopularSitesSuggestions(
547 const MostVisitedSites::SuggestionsVector& personal_suggestions,
548 const MostVisitedSites::SuggestionsVector& whitelist_suggestions) {
549 size_t num_suggestions =
550 personal_suggestions.size() + whitelist_suggestions.size();
551 DCHECK_LE(num_suggestions, static_cast<size_t>(num_sites_));
552
knncbbd754d2015-09-09 15:43:40553 // Collect non-blacklisted popular suggestions, skipping those already present
554 // in the personal suggestions.
atanasova9572aaf2016-02-26 18:08:26555 size_t num_popular_sites_suggestions = num_sites_ - num_suggestions;
556 MostVisitedSites::SuggestionsVector popular_sites_suggestions;
treibcffa6502015-08-06 09:12:27557
atanasova9572aaf2016-02-26 18:08:26558 if (num_popular_sites_suggestions > 0 && popular_sites_) {
knncbbd754d2015-09-09 15:43:40559 std::set<std::string> personal_hosts;
atanasova9572aaf2016-02-26 18:08:26560 for (const auto& suggestion : personal_suggestions)
knncbbd754d2015-09-09 15:43:40561 personal_hosts.insert(suggestion->url.host());
562 scoped_refptr<TopSites> top_sites(TopSitesFactory::GetForProfile(profile_));
563 for (const PopularSites::Site& popular_site : popular_sites_->sites()) {
564 // Skip blacklisted sites.
565 if (top_sites && top_sites->IsBlacklisted(popular_site.url))
566 continue;
567 std::string host = popular_site.url.host();
568 // Skip suggestions already present in personal.
569 if (personal_hosts.find(host) != personal_hosts.end())
570 continue;
treibcffa6502015-08-06 09:12:27571
atanasova9572aaf2016-02-26 18:08:26572 popular_sites_suggestions.push_back(make_scoped_ptr(
573 new Suggestion(popular_site.title, popular_site.url, POPULAR)));
574 if (popular_sites_suggestions.size() >= num_popular_sites_suggestions)
knncbbd754d2015-09-09 15:43:40575 break;
576 }
treibcffa6502015-08-06 09:12:27577 }
atanasova9572aaf2016-02-26 18:08:26578 return popular_sites_suggestions;
579}
580
581void MostVisitedSites::SaveNewNTPSuggestions(
582 MostVisitedSites::SuggestionsVector* personal_suggestions) {
583 MostVisitedSites::SuggestionsVector whitelist_suggestions =
584 CreateWhitelistEntryPointSuggestions(*personal_suggestions);
585 MostVisitedSites::SuggestionsVector popular_sites_suggestions =
586 CreatePopularSitesSuggestions(*personal_suggestions,
587 whitelist_suggestions);
588 size_t num_actual_tiles = personal_suggestions->size() +
589 whitelist_suggestions.size() +
590 popular_sites_suggestions.size();
knncbbd754d2015-09-09 15:43:40591 std::vector<std::string> old_sites_url;
592 std::vector<bool> old_sites_is_personal;
593 GetPreviousNTPSites(num_actual_tiles, &old_sites_url, &old_sites_is_personal);
atanasova9572aaf2016-02-26 18:08:26594 MostVisitedSites::SuggestionsVector merged_suggestions = MergeSuggestions(
595 personal_suggestions, &whitelist_suggestions, &popular_sites_suggestions,
596 old_sites_url, old_sites_is_personal);
knncbbd754d2015-09-09 15:43:40597 DCHECK_EQ(num_actual_tiles, merged_suggestions.size());
598 current_suggestions_.swap(merged_suggestions);
599 if (received_popular_sites_)
600 SaveCurrentNTPSites();
treibcf674e62015-08-17 15:43:01601}
602
603// static
atanasova9572aaf2016-02-26 18:08:26604MostVisitedSites::SuggestionsVector MostVisitedSites::MergeSuggestions(
605 MostVisitedSites::SuggestionsVector* personal_suggestions,
606 MostVisitedSites::SuggestionsVector* whitelist_suggestions,
607 MostVisitedSites::SuggestionsVector* popular_suggestions,
knncbbd754d2015-09-09 15:43:40608 const std::vector<std::string>& old_sites_url,
609 const std::vector<bool>& old_sites_is_personal) {
610 size_t num_personal_suggestions = personal_suggestions->size();
atanasova9572aaf2016-02-26 18:08:26611 size_t num_whitelist_suggestions = whitelist_suggestions->size();
knncbbd754d2015-09-09 15:43:40612 size_t num_popular_suggestions = popular_suggestions->size();
atanasova9572aaf2016-02-26 18:08:26613 size_t num_tiles = num_popular_suggestions + num_whitelist_suggestions +
614 num_personal_suggestions;
615 MostVisitedSites::SuggestionsVector merged_suggestions;
knncbbd754d2015-09-09 15:43:40616 merged_suggestions.resize(num_tiles);
treibcf674e62015-08-17 15:43:01617
knncbbd754d2015-09-09 15:43:40618 size_t num_old_tiles = old_sites_url.size();
619 DCHECK_LE(num_old_tiles, num_tiles);
620 DCHECK_EQ(num_old_tiles, old_sites_is_personal.size());
621 std::vector<std::string> old_sites_host;
622 old_sites_host.reserve(num_old_tiles);
623 // Only populate the hosts for popular suggestions as only they can be
624 // replaced by host. Personal suggestions require an exact url match to be
625 // replaced.
626 for (size_t i = 0; i < num_old_tiles; ++i) {
627 old_sites_host.push_back(old_sites_is_personal[i]
628 ? std::string()
629 : GURL(old_sites_url[i]).host());
treibcf674e62015-08-17 15:43:01630 }
631
knncbbd754d2015-09-09 15:43:40632 // Insert personal suggestions if they existed previously.
633 std::vector<size_t> new_personal_suggestions = InsertMatchingSuggestions(
634 personal_suggestions, &merged_suggestions, old_sites_url, old_sites_host);
atanasova9572aaf2016-02-26 18:08:26635 // Insert whitelist suggestions if they existed previously.
636 std::vector<size_t> new_whitelist_suggestions =
637 InsertMatchingSuggestions(whitelist_suggestions, &merged_suggestions,
638 old_sites_url, old_sites_host);
knncbbd754d2015-09-09 15:43:40639 // Insert popular suggestions if they existed previously.
640 std::vector<size_t> new_popular_suggestions = InsertMatchingSuggestions(
641 popular_suggestions, &merged_suggestions, old_sites_url, old_sites_host);
642 // Insert leftover personal suggestions.
643 size_t filled_so_far = InsertAllSuggestions(
644 0, new_personal_suggestions, personal_suggestions, &merged_suggestions);
atanasova9572aaf2016-02-26 18:08:26645 // Insert leftover whitelist suggestions.
646 filled_so_far =
647 InsertAllSuggestions(filled_so_far, new_whitelist_suggestions,
648 whitelist_suggestions, &merged_suggestions);
knncbbd754d2015-09-09 15:43:40649 // Insert leftover popular suggestions.
650 InsertAllSuggestions(filled_so_far, new_popular_suggestions,
651 popular_suggestions, &merged_suggestions);
dchengbc6125d2015-12-30 01:54:07652 return merged_suggestions;
treibcffa6502015-08-06 09:12:27653}
654
knncbbd754d2015-09-09 15:43:40655void MostVisitedSites::GetPreviousNTPSites(
656 size_t num_tiles,
657 std::vector<std::string>* old_sites_url,
658 std::vector<bool>* old_sites_is_personal) const {
659 const PrefService* prefs = profile_->GetPrefs();
660 const base::ListValue* url_list = prefs->GetList(prefs::kNTPSuggestionsURL);
661 const base::ListValue* source_list =
662 prefs->GetList(prefs::kNTPSuggestionsIsPersonal);
663 DCHECK_EQ(url_list->GetSize(), source_list->GetSize());
664 if (url_list->GetSize() < num_tiles)
665 num_tiles = url_list->GetSize();
666 if (num_tiles == 0) {
667 // No fallback required as Personal suggestions take precedence anyway.
668 return;
669 }
670 old_sites_url->reserve(num_tiles);
671 old_sites_is_personal->reserve(num_tiles);
672 for (size_t i = 0; i < num_tiles; ++i) {
673 std::string url_string;
674 bool success = url_list->GetString(i, &url_string);
675 DCHECK(success);
676 old_sites_url->push_back(url_string);
677 bool is_personal;
678 success = source_list->GetBoolean(i, &is_personal);
679 DCHECK(success);
680 old_sites_is_personal->push_back(is_personal);
681 }
682}
683
684void MostVisitedSites::SaveCurrentNTPSites() {
685 base::ListValue url_list;
686 base::ListValue source_list;
atanasova9572aaf2016-02-26 18:08:26687 for (const auto& suggestion : current_suggestions_) {
knncbbd754d2015-09-09 15:43:40688 url_list.AppendString(suggestion->url.spec());
689 source_list.AppendBoolean(suggestion->source != MostVisitedSites::POPULAR);
690 }
691 PrefService* prefs = profile_->GetPrefs();
692 prefs->Set(prefs::kNTPSuggestionsIsPersonal, source_list);
693 prefs->Set(prefs::kNTPSuggestionsURL, url_list);
694}
695
696// static
697std::vector<size_t> MostVisitedSites::InsertMatchingSuggestions(
atanasova9572aaf2016-02-26 18:08:26698 MostVisitedSites::SuggestionsVector* src_suggestions,
699 MostVisitedSites::SuggestionsVector* dst_suggestions,
knncbbd754d2015-09-09 15:43:40700 const std::vector<std::string>& match_urls,
701 const std::vector<std::string>& match_hosts) {
702 std::vector<size_t> unmatched_suggestions;
703 size_t num_src_suggestions = src_suggestions->size();
704 size_t num_matchers = match_urls.size();
705 for (size_t i = 0; i < num_src_suggestions; ++i) {
706 size_t position;
707 for (position = 0; position < num_matchers; ++position) {
708 if ((*dst_suggestions)[position] != nullptr)
709 continue;
710 if (match_urls[position] == (*src_suggestions)[i]->url.spec())
711 break;
712 // match_hosts is only populated for suggestions which can be replaced by
713 // host matching like popular suggestions.
714 if (match_hosts[position] == (*src_suggestions)[i]->url.host())
715 break;
716 }
717 if (position == num_matchers) {
718 unmatched_suggestions.push_back(i);
719 } else {
720 // A move is required as the source and destination containers own the
721 // elements.
722 std::swap((*dst_suggestions)[position], (*src_suggestions)[i]);
723 }
724 }
725 return unmatched_suggestions;
726}
727
728// static
729size_t MostVisitedSites::InsertAllSuggestions(
730 size_t start_position,
731 const std::vector<size_t>& insert_positions,
atanasova9572aaf2016-02-26 18:08:26732 std::vector<scoped_ptr<Suggestion>>* src_suggestions,
733 std::vector<scoped_ptr<Suggestion>>* dst_suggestions) {
knncbbd754d2015-09-09 15:43:40734 size_t num_inserts = insert_positions.size();
735 size_t num_dests = dst_suggestions->size();
736
737 size_t src_pos = 0;
738 size_t i = start_position;
739 for (; i < num_dests && src_pos < num_inserts; ++i) {
740 if ((*dst_suggestions)[i] != nullptr)
741 continue;
742 size_t src = insert_positions[src_pos++];
743 std::swap((*dst_suggestions)[i], (*src_suggestions)[src]);
744 }
745 // Return destination postions filled so far which becomes the start_position
746 // for future runs.
747 return i;
748}
749
750void MostVisitedSites::NotifyMostVisitedURLsObserver() {
751 size_t num_suggestions = current_suggestions_.size();
treib783a3372015-08-25 16:24:39752 if (received_most_visited_sites_ && received_popular_sites_ &&
753 !recorded_uma_) {
754 RecordImpressionUMAMetrics();
newt2a47ce72015-10-01 20:09:49755 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.NumberOfTiles", num_suggestions);
treib783a3372015-08-25 16:24:39756 recorded_uma_ = true;
757 }
knncbbd754d2015-09-09 15:43:40758 std::vector<base::string16> titles;
759 std::vector<std::string> urls;
760 titles.reserve(num_suggestions);
761 urls.reserve(num_suggestions);
atanasova9572aaf2016-02-26 18:08:26762 for (const auto& suggestion : current_suggestions_) {
knncbbd754d2015-09-09 15:43:40763 titles.push_back(suggestion->title);
764 urls.push_back(suggestion->url.spec());
765 }
[email protected]bc093e82014-03-04 21:49:11766 JNIEnv* env = AttachCurrentThread();
knncbbd754d2015-09-09 15:43:40767 DCHECK_EQ(titles.size(), urls.size());
[email protected]bc093e82014-03-04 21:49:11768 Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
knn6cf777fc2015-05-06 13:21:48769 env, observer_.obj(), ToJavaArrayOfStrings(env, titles).obj(),
[email protected]bc093e82014-03-04 21:49:11770 ToJavaArrayOfStrings(env, urls).obj());
771}
772
treibcffa6502015-08-06 09:12:27773void MostVisitedSites::OnPopularSitesAvailable(bool success) {
treib783a3372015-08-25 16:24:39774 received_popular_sites_ = true;
775
treibcffa6502015-08-06 09:12:27776 if (!success) {
777 LOG(WARNING) << "Download of popular sites failed";
778 return;
779 }
780
treib7f5cf872015-08-11 13:11:47781 if (observer_.is_null())
782 return;
783
784 std::vector<std::string> urls;
785 std::vector<std::string> favicon_urls;
knn7d8192222015-10-12 20:37:59786 std::vector<std::string> large_icon_urls;
treib7f5cf872015-08-11 13:11:47787 for (const PopularSites::Site& popular_site : popular_sites_->sites()) {
788 urls.push_back(popular_site.url.spec());
789 favicon_urls.push_back(popular_site.favicon_url.spec());
knn7d8192222015-10-12 20:37:59790 large_icon_urls.push_back(popular_site.large_icon_url.spec());
treib7f5cf872015-08-11 13:11:47791 }
792 JNIEnv* env = AttachCurrentThread();
793 Java_MostVisitedURLsObserver_onPopularURLsAvailable(
794 env, observer_.obj(), ToJavaArrayOfStrings(env, urls).obj(),
knn7d8192222015-10-12 20:37:59795 ToJavaArrayOfStrings(env, favicon_urls).obj(),
796 ToJavaArrayOfStrings(env, large_icon_urls).obj());
treib7f5cf872015-08-11 13:11:47797 QueryMostVisitedURLs();
treibcffa6502015-08-06 09:12:27798}
799
treib783a3372015-08-25 16:24:39800void MostVisitedSites::RecordImpressionUMAMetrics() {
knncbbd754d2015-09-09 15:43:40801 for (size_t i = 0; i < current_suggestions_.size(); i++) {
802 std::string histogram = base::StringPrintf(
newt2a47ce72015-10-01 20:09:49803 "NewTabPage.SuggestionsImpression.%s",
knncbbd754d2015-09-09 15:43:40804 current_suggestions_[i]->GetSourceHistogramName().c_str());
treib783a3372015-08-25 16:24:39805 LogHistogramEvent(histogram, static_cast<int>(i), num_sites_);
806 }
807}
808
sdefresneedf9e01f2015-01-13 19:45:41809void MostVisitedSites::TopSitesLoaded(history::TopSites* top_sites) {
810}
811
fserbdb575112015-06-29 21:31:59812void MostVisitedSites::TopSitesChanged(history::TopSites* top_sites,
813 ChangeReason change_reason) {
sdefresneedf9e01f2015-01-13 19:45:41814 if (mv_source_ == TOP_SITES) {
815 // The displayed suggestions are invalidated.
newt2a47ce72015-10-01 20:09:49816 InitiateTopSitesQuery();
sdefresneedf9e01f2015-01-13 19:45:41817 }
818}
819
torne89cc5d92015-09-04 11:16:35820static jlong Init(JNIEnv* env,
821 const JavaParamRef<jobject>& obj,
822 const JavaParamRef<jobject>& jprofile) {
[email protected]7ceb4e32013-12-06 04:13:04823 MostVisitedSites* most_visited_sites =
824 new MostVisitedSites(ProfileAndroid::FromProfileAndroid(jprofile));
825 return reinterpret_cast<intptr_t>(most_visited_sites);
826}