blob: 03fb06c4781e12b829d7b0520c01d73148923153 [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
7#include "base/android/jni_android.h"
8#include "base/android/jni_array.h"
9#include "base/android/jni_string.h"
10#include "base/android/scoped_java_ref.h"
[email protected]d33adb40d2014-06-03 21:38:4611#include "base/callback.h"
treibcffa6502015-08-06 09:12:2712#include "base/command_line.h"
[email protected]9ed2f5c2014-06-13 14:55:2413#include "base/metrics/histogram.h"
[email protected]1d778e12014-06-17 02:28:5114#include "base/metrics/sparse_histogram.h"
knncbbd754d2015-09-09 15:43:4015#include "base/prefs/pref_service.h"
[email protected]9ed2f5c2014-06-13 14:55:2416#include "base/strings/string_number_conversions.h"
treibeba6cee2015-09-09 13:25:5917#include "base/strings/string_util.h"
[email protected]9ed2f5c2014-06-13 14:55:2418#include "base/strings/stringprintf.h"
[email protected]bc093e82014-03-04 21:49:1119#include "base/strings/utf_string_conversions.h"
[email protected]6d21c702014-05-15 06:10:2120#include "base/time/time.h"
treibcffa6502015-08-06 09:12:2721#include "chrome/browser/android/popular_sites.h"
jitendra.ks30f03392015-01-28 09:47:1822#include "chrome/browser/history/top_sites_factory.h"
[email protected]c764b2822013-07-03 04:16:4823#include "chrome/browser/profiles/profile.h"
24#include "chrome/browser/profiles/profile_android.h"
[email protected]bc093e82014-03-04 21:49:1125#include "chrome/browser/search/suggestions/suggestions_service_factory.h"
[email protected]81e09e72014-05-07 13:41:2226#include "chrome/browser/search/suggestions/suggestions_source.h"
[email protected]1d1ab152014-08-21 17:14:1227#include "chrome/browser/sync/profile_sync_service.h"
28#include "chrome/browser/sync/profile_sync_service_factory.h"
[email protected]81e09e72014-05-07 13:41:2229#include "chrome/browser/thumbnails/thumbnail_list_source.h"
treibcffa6502015-08-06 09:12:2730#include "chrome/common/chrome_switches.h"
knncbbd754d2015-09-09 15:43:4031#include "chrome/common/pref_names.h"
sdefresne0da3bc02015-01-29 18:26:3532#include "components/history/core/browser/top_sites.h"
knncbbd754d2015-09-09 15:43:4033#include "components/pref_registry/pref_registry_syncable.h"
[email protected]bdceb3ba2014-07-25 16:47:4834#include "components/suggestions/suggestions_service.h"
[email protected]1d1ab152014-08-21 17:14:1235#include "components/suggestions/suggestions_utils.h"
[email protected]c764b2822013-07-03 04:16:4836#include "content/public/browser/browser_thread.h"
[email protected]81e09e72014-05-07 13:41:2237#include "content/public/browser/url_data_source.h"
[email protected]c764b2822013-07-03 04:16:4838#include "jni/MostVisitedSites_jni.h"
39#include "third_party/skia/include/core/SkBitmap.h"
40#include "ui/gfx/android/java_bitmap.h"
41#include "ui/gfx/codec/jpeg_codec.h"
treibcffa6502015-08-06 09:12:2742#include "url/gurl.h"
[email protected]c764b2822013-07-03 04:16:4843
44using base::android::AttachCurrentThread;
[email protected]c764b2822013-07-03 04:16:4845using base::android::ConvertJavaStringToUTF8;
46using base::android::ScopedJavaGlobalRef;
knn6cf777fc2015-05-06 13:21:4847using base::android::ScopedJavaLocalRef;
[email protected]c764b2822013-07-03 04:16:4848using base::android::ToJavaArrayOfStrings;
[email protected]c764b2822013-07-03 04:16:4849using content::BrowserThread;
50using history::TopSites;
[email protected]bc093e82014-03-04 21:49:1151using suggestions::ChromeSuggestion;
52using suggestions::SuggestionsProfile;
53using suggestions::SuggestionsService;
54using suggestions::SuggestionsServiceFactory;
[email protected]1d1ab152014-08-21 17:14:1255using suggestions::SyncState;
[email protected]c764b2822013-07-03 04:16:4856
[email protected]c764b2822013-07-03 04:16:4857namespace {
58
[email protected]92a598822014-06-17 10:25:5659// Total number of tiles displayed.
[email protected]9ed2f5c2014-06-13 14:55:2460const char kNumTilesHistogramName[] = "NewTabPage.NumberOfTiles";
[email protected]92a598822014-06-17 10:25:5661// Tracking thumbnails.
[email protected]1d778e12014-06-17 02:28:5162const char kNumLocalThumbnailTilesHistogramName[] =
63 "NewTabPage.NumberOfThumbnailTiles";
64const char kNumEmptyTilesHistogramName[] = "NewTabPage.NumberOfGrayTiles";
65const char kNumServerTilesHistogramName[] = "NewTabPage.NumberOfExternalTiles";
treib783a3372015-08-25 16:24:3966
67// Format for tile clicks histogram.
68const char kOpenedItemHistogramFormat[] = "NewTabPage.MostVisited.%s";
69// Format for tile impressions histogram.
70const char kImpressionHistogramFormat[] = "NewTabPage.SuggestionsImpression.%s";
71// Identifiers for the various tile sources.
72const char kHistogramClientName[] = "client";
73const char kHistogramServerName[] = "server";
74const char kHistogramServerFormat[] = "server%d";
75const char kHistogramPopularName[] = "popular";
[email protected]9ed2f5c2014-06-13 14:55:2476
treib508939a2015-08-25 10:07:5677const char kPopularSitesFieldTrialName[] = "NTPPopularSites";
78
knn6cf777fc2015-05-06 13:21:4879scoped_ptr<SkBitmap> MaybeFetchLocalThumbnail(
80 const GURL& url,
81 const scoped_refptr<TopSites>& top_sites) {
82 DCHECK_CURRENTLY_ON(BrowserThread::DB);
83 scoped_refptr<base::RefCountedMemory> image;
84 scoped_ptr<SkBitmap> bitmap;
85 if (top_sites && top_sites->GetPageThumbnail(url, false, &image))
86 bitmap.reset(gfx::JPEGCodec::Decode(image->front(), image->size()));
87 return bitmap.Pass();
[email protected]d33adb40d2014-06-03 21:38:4688}
89
[email protected]92a598822014-06-17 10:25:5690// Log an event for a given |histogram| at a given element |position|. This
91// routine exists because regular histogram macros are cached thus can't be used
92// if the name of the histogram will change at a given call site.
treib783a3372015-08-25 16:24:3993void LogHistogramEvent(const std::string& histogram,
94 int position,
[email protected]92a598822014-06-17 10:25:5695 int num_sites) {
[email protected]9ed2f5c2014-06-13 14:55:2496 base::HistogramBase* counter = base::LinearHistogram::FactoryGet(
[email protected]92a598822014-06-17 10:25:5697 histogram,
[email protected]9ed2f5c2014-06-13 14:55:2498 1,
99 num_sites,
100 num_sites + 1,
101 base::Histogram::kUmaTargetedHistogramFlag);
[email protected]6f674482014-08-11 22:50:49102 if (counter)
103 counter->Add(position);
[email protected]9ed2f5c2014-06-13 14:55:24104}
105
[email protected]1d1ab152014-08-21 17:14:12106// Return the current SyncState for use with the SuggestionsService.
107SyncState GetSyncState(Profile* profile) {
108 ProfileSyncService* sync =
109 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
110 if (!sync)
111 return SyncState::SYNC_OR_HISTORY_SYNC_DISABLED;
112 return suggestions::GetSyncState(
maxbogue96b7d272015-06-16 02:59:58113 sync->CanSyncStart(),
maxbogue192b3602015-06-04 00:33:55114 sync->IsSyncActive() && sync->ConfigurationDone(),
[email protected]1d1ab152014-08-21 17:14:12115 sync->GetActiveDataTypes().Has(syncer::HISTORY_DELETE_DIRECTIVES));
116}
117
treibcffa6502015-08-06 09:12:27118bool ShouldShowPopularSites() {
119 // Note: It's important to query the field trial state first, to ensure that
120 // UMA reports the correct group.
121 const std::string group_name =
treib508939a2015-08-25 10:07:56122 base::FieldTrialList::FindFullName(kPopularSitesFieldTrialName);
treibcffa6502015-08-06 09:12:27123 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
124 if (cmd_line->HasSwitch(switches::kDisableNTPPopularSites))
125 return false;
126 if (cmd_line->HasSwitch(switches::kEnableNTPPopularSites))
127 return true;
treibeba6cee2015-09-09 13:25:59128 return base::StartsWith(group_name, "Enabled",
129 base::CompareCase::INSENSITIVE_ASCII);
treibcffa6502015-08-06 09:12:27130}
131
treib508939a2015-08-25 10:07:56132std::string GetPopularSitesFilename() {
133 return variations::GetVariationParamValue(kPopularSitesFieldTrialName,
134 "filename");
135}
136
[email protected]c764b2822013-07-03 04:16:48137} // namespace
138
knncbbd754d2015-09-09 15:43:40139MostVisitedSites::Suggestion::Suggestion(const base::string16& title,
140 const std::string& url,
141 MostVisitedSource source)
142 : title(title), url(url), source(source), provider_index(-1) {}
143
144MostVisitedSites::Suggestion::Suggestion(const base::string16& title,
145 const GURL& url,
146 MostVisitedSource source)
147 : title(title), url(url), source(source), provider_index(-1) {}
148
149MostVisitedSites::Suggestion::Suggestion(const base::string16& title,
150 const std::string& url,
151 MostVisitedSource source,
152 int provider_index)
153 : title(title), url(url), source(source), provider_index(provider_index) {
154 DCHECK_EQ(MostVisitedSites::SUGGESTIONS_SERVICE, source);
155}
156
157MostVisitedSites::Suggestion::~Suggestion() {}
158
159std::string MostVisitedSites::Suggestion::GetSourceHistogramName() const {
160 switch (source) {
161 case MostVisitedSites::TOP_SITES:
162 return kHistogramClientName;
163 case MostVisitedSites::POPULAR:
164 return kHistogramPopularName;
165 case MostVisitedSites::SUGGESTIONS_SERVICE:
166 return provider_index >= 0
167 ? base::StringPrintf(kHistogramServerFormat, provider_index)
168 : kHistogramServerName;
169 }
170 NOTREACHED();
171 return std::string();
172}
173
[email protected]7ceb4e32013-12-06 04:13:04174MostVisitedSites::MostVisitedSites(Profile* profile)
treib783a3372015-08-25 16:24:39175 : profile_(profile), num_sites_(0), received_most_visited_sites_(false),
176 received_popular_sites_(false), recorded_uma_(false),
mathpd91e8eb2015-03-24 17:37:47177 num_local_thumbs_(0), num_server_thumbs_(0), num_empty_thumbs_(0),
178 scoped_observer_(this), weak_ptr_factory_(this) {
[email protected]81e09e72014-05-07 13:41:22179 // Register the debugging page for the Suggestions Service and the thumbnails
180 // debugging page.
181 content::URLDataSource::Add(profile_,
182 new suggestions::SuggestionsSource(profile_));
183 content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_));
[email protected]1d1ab152014-08-21 17:14:12184
185 // Register this class as an observer to the sync service. It is important to
186 // be notified of changes in the sync state such as initialization, sync
187 // being enabled or disabled, etc.
188 ProfileSyncService* profile_sync_service =
189 ProfileSyncServiceFactory::GetForProfile(profile_);
190 if (profile_sync_service)
191 profile_sync_service->AddObserver(this);
treibcffa6502015-08-06 09:12:27192
193 if (ShouldShowPopularSites()) {
194 popular_sites_.reset(new PopularSites(
maybellea18d7d02015-09-08 14:28:25195 profile,
treib508939a2015-08-25 10:07:56196 GetPopularSitesFilename(),
treibcffa6502015-08-06 09:12:27197 profile_->GetRequestContext(),
198 base::Bind(&MostVisitedSites::OnPopularSitesAvailable,
199 base::Unretained(this))));
treib783a3372015-08-25 16:24:39200 } else {
201 received_popular_sites_ = true;
treibcffa6502015-08-06 09:12:27202 }
[email protected]7ceb4e32013-12-06 04:13:04203}
[email protected]c764b2822013-07-03 04:16:48204
[email protected]7ceb4e32013-12-06 04:13:04205MostVisitedSites::~MostVisitedSites() {
feng94a42102014-08-26 23:42:39206 ProfileSyncService* profile_sync_service =
207 ProfileSyncServiceFactory::GetForProfile(profile_);
208 if (profile_sync_service && profile_sync_service->HasObserver(this))
209 profile_sync_service->RemoveObserver(this);
[email protected]7ceb4e32013-12-06 04:13:04210}
[email protected]c764b2822013-07-03 04:16:48211
[email protected]7ceb4e32013-12-06 04:13:04212void MostVisitedSites::Destroy(JNIEnv* env, jobject obj) {
213 delete this;
214}
[email protected]c764b2822013-07-03 04:16:48215
[email protected]1d778e12014-06-17 02:28:51216void MostVisitedSites::OnLoadingComplete(JNIEnv* env, jobject obj) {
treib783a3372015-08-25 16:24:39217 RecordThumbnailUMAMetrics();
[email protected]1d778e12014-06-17 02:28:51218}
219
[email protected]7ceb4e32013-12-06 04:13:04220void MostVisitedSites::SetMostVisitedURLsObserver(JNIEnv* env,
221 jobject obj,
222 jobject j_observer,
223 jint num_sites) {
224 observer_.Reset(env, j_observer);
225 num_sites_ = num_sites;
[email protected]852fc792013-10-04 04:15:12226
[email protected]7ceb4e32013-12-06 04:13:04227 QueryMostVisitedURLs();
228
jitendra.ks30f03392015-01-28 09:47:18229 scoped_refptr<history::TopSites> top_sites =
230 TopSitesFactory::GetForProfile(profile_);
[email protected]7ceb4e32013-12-06 04:13:04231 if (top_sites) {
232 // TopSites updates itself after a delay. To ensure up-to-date results,
233 // force an update now.
234 top_sites->SyncWithHistory();
235
sdefresneedf9e01f2015-01-13 19:45:41236 // Register as TopSitesObserver so that we can update ourselves when the
237 // TopSites changes.
jitendra.ks30f03392015-01-28 09:47:18238 scoped_observer_.Add(top_sites.get());
[email protected]7ceb4e32013-12-06 04:13:04239 }
[email protected]c764b2822013-07-03 04:16:48240}
241
[email protected]7ceb4e32013-12-06 04:13:04242void MostVisitedSites::GetURLThumbnail(JNIEnv* env,
243 jobject obj,
knn6cf777fc2015-05-06 13:21:48244 jstring j_url,
[email protected]7ceb4e32013-12-06 04:13:04245 jobject j_callback_obj) {
mostynb351bf982015-03-25 22:26:58246 DCHECK_CURRENTLY_ON(BrowserThread::UI);
knn6cf777fc2015-05-06 13:21:48247 scoped_ptr<ScopedJavaGlobalRef<jobject>> j_callback(
248 new ScopedJavaGlobalRef<jobject>());
[email protected]7ceb4e32013-12-06 04:13:04249 j_callback->Reset(env, j_callback_obj);
[email protected]c764b2822013-07-03 04:16:48250
knn6cf777fc2015-05-06 13:21:48251 GURL url(ConvertJavaStringToUTF8(env, j_url));
jitendra.ks30f03392015-01-28 09:47:18252 scoped_refptr<TopSites> top_sites(TopSitesFactory::GetForProfile(profile_));
[email protected]d33adb40d2014-06-03 21:38:46253
knn6cf777fc2015-05-06 13:21:48254 BrowserThread::PostTaskAndReplyWithResult(
[email protected]d33adb40d2014-06-03 21:38:46255 BrowserThread::DB, FROM_HERE,
knn6cf777fc2015-05-06 13:21:48256 base::Bind(&MaybeFetchLocalThumbnail, url, top_sites),
257 base::Bind(&MostVisitedSites::OnLocalThumbnailFetched,
258 weak_ptr_factory_.GetWeakPtr(), url,
259 base::Passed(&j_callback)));
260}
261
262void MostVisitedSites::OnLocalThumbnailFetched(
263 const GURL& url,
264 scoped_ptr<ScopedJavaGlobalRef<jobject>> j_callback,
265 scoped_ptr<SkBitmap> bitmap) {
266 DCHECK_CURRENTLY_ON(BrowserThread::UI);
267 if (!bitmap.get()) {
268 // A thumbnail is not locally available for |url|. Make sure it is put in
269 // the list to be fetched at the next visit to this site.
270 scoped_refptr<TopSites> top_sites(TopSitesFactory::GetForProfile(profile_));
271 if (top_sites)
272 top_sites->AddForcedURL(url, base::Time::Now());
treibac5372f2015-09-09 09:08:22273 // Also fetch a remote thumbnail if possible. PopularSites or the
274 // SuggestionsService can supply a thumbnail download URL.
knn6cf777fc2015-05-06 13:21:48275 SuggestionsService* suggestions_service =
treibac5372f2015-09-09 09:08:22276 SuggestionsServiceFactory::GetForProfile(profile_);
knn6cf777fc2015-05-06 13:21:48277 if (suggestions_service) {
treibac5372f2015-09-09 09:08:22278 if (popular_sites_) {
279 const std::vector<PopularSites::Site>& sites = popular_sites_->sites();
280 auto it = std::find_if(sites.begin(), sites.end(),
281 [&url](const PopularSites::Site& site) {
282 return site.url == url;
283 });
284 if (it != sites.end() && it->thumbnail_url.is_valid()) {
285 return suggestions_service->GetPageThumbnailWithURL(
286 url, it->thumbnail_url,
287 base::Bind(&MostVisitedSites::OnObtainedThumbnail,
288 weak_ptr_factory_.GetWeakPtr(), false,
289 base::Passed(&j_callback)));
290 }
291 }
292 if (mv_source_ == SUGGESTIONS_SERVICE) {
293 return suggestions_service->GetPageThumbnail(
294 url, base::Bind(&MostVisitedSites::OnObtainedThumbnail,
295 weak_ptr_factory_.GetWeakPtr(), false,
296 base::Passed(&j_callback)));
297 }
knn6cf777fc2015-05-06 13:21:48298 }
299 }
300 OnObtainedThumbnail(true, j_callback.Pass(), url, bitmap.get());
301}
302
303void MostVisitedSites::OnObtainedThumbnail(
304 bool is_local_thumbnail,
305 scoped_ptr<ScopedJavaGlobalRef<jobject>> j_callback,
306 const GURL& url,
307 const SkBitmap* bitmap) {
308 DCHECK_CURRENTLY_ON(BrowserThread::UI);
309 JNIEnv* env = AttachCurrentThread();
310 ScopedJavaLocalRef<jobject> j_bitmap;
311 if (bitmap) {
312 j_bitmap = gfx::ConvertToJavaBitmap(bitmap);
313 if (is_local_thumbnail) {
314 ++num_local_thumbs_;
315 } else {
316 ++num_server_thumbs_;
317 }
318 } else {
319 ++num_empty_thumbs_;
320 }
321 Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
322 env, j_callback->obj(), j_bitmap.obj());
[email protected]c764b2822013-07-03 04:16:48323}
[email protected]a4c2f282013-07-20 05:26:05324
[email protected]7ceb4e32013-12-06 04:13:04325void MostVisitedSites::BlacklistUrl(JNIEnv* env,
326 jobject obj,
327 jstring j_url) {
knn6cf777fc2015-05-06 13:21:48328 GURL url(ConvertJavaStringToUTF8(env, j_url));
[email protected]a4c2f282013-07-20 05:26:05329
treibfac75192015-08-17 13:21:24330 // Always blacklist in the local TopSites.
331 scoped_refptr<TopSites> top_sites = TopSitesFactory::GetForProfile(profile_);
332 if (top_sites)
333 top_sites->AddBlacklistedURL(url);
[email protected]d4975242014-06-02 18:16:20334
treibfac75192015-08-17 13:21:24335 // Only blacklist in the server-side suggestions service if it's active.
336 if (mv_source_ == SUGGESTIONS_SERVICE) {
337 SuggestionsService* suggestions_service =
338 SuggestionsServiceFactory::GetForProfile(profile_);
339 DCHECK(suggestions_service);
340 suggestions_service->BlacklistURL(
341 url, base::Bind(&MostVisitedSites::OnSuggestionsProfileAvailable,
342 weak_ptr_factory_.GetWeakPtr()),
343 base::Closure());
[email protected]d4975242014-06-02 18:16:20344 }
[email protected]a4c2f282013-07-20 05:26:05345}
[email protected]7ceb4e32013-12-06 04:13:04346
[email protected]92a598822014-06-17 10:25:56347void MostVisitedSites::RecordOpenedMostVisitedItem(JNIEnv* env,
348 jobject obj,
349 jint index) {
treib783a3372015-08-25 16:24:39350 DCHECK_GE(index, 0);
knncbbd754d2015-09-09 15:43:40351 DCHECK_LT(index, static_cast<int>(current_suggestions_.size()));
352 std::string histogram = base::StringPrintf(
353 kOpenedItemHistogramFormat,
354 current_suggestions_[index]->GetSourceHistogramName().c_str());
treib783a3372015-08-25 16:24:39355 LogHistogramEvent(histogram, index, num_sites_);
[email protected]92a598822014-06-17 10:25:56356}
357
[email protected]1d1ab152014-08-21 17:14:12358void MostVisitedSites::OnStateChanged() {
359 // There have been changes to the sync state. This class cares about a few
360 // (just initialized, enabled/disabled or history sync state changed). Re-run
361 // the query code which will use the proper state.
362 QueryMostVisitedURLs();
363}
364
[email protected]7ceb4e32013-12-06 04:13:04365// static
366bool MostVisitedSites::Register(JNIEnv* env) {
367 return RegisterNativesImpl(env);
368}
369
knncbbd754d2015-09-09 15:43:40370// static
371void MostVisitedSites::RegisterProfilePrefs(
372 user_prefs::PrefRegistrySyncable* registry) {
373 registry->RegisterListPref(prefs::kNTPSuggestionsURL);
374 registry->RegisterListPref(prefs::kNTPSuggestionsIsPersonal);
375}
376
[email protected]7ceb4e32013-12-06 04:13:04377void MostVisitedSites::QueryMostVisitedURLs() {
[email protected]bc093e82014-03-04 21:49:11378 SuggestionsService* suggestions_service =
[email protected]d4975242014-06-02 18:16:20379 SuggestionsServiceFactory::GetForProfile(profile_);
[email protected]bc093e82014-03-04 21:49:11380 if (suggestions_service) {
381 // Suggestions service is enabled, initiate a query.
382 suggestions_service->FetchSuggestionsData(
[email protected]1d1ab152014-08-21 17:14:12383 GetSyncState(profile_),
knn6cf777fc2015-05-06 13:21:48384 base::Bind(&MostVisitedSites::OnSuggestionsProfileAvailable,
385 weak_ptr_factory_.GetWeakPtr()));
[email protected]bc093e82014-03-04 21:49:11386 } else {
387 InitiateTopSitesQuery();
388 }
389}
390
391void MostVisitedSites::InitiateTopSitesQuery() {
jitendra.ks30f03392015-01-28 09:47:18392 scoped_refptr<TopSites> top_sites = TopSitesFactory::GetForProfile(profile_);
[email protected]7ceb4e32013-12-06 04:13:04393 if (!top_sites)
394 return;
395
396 top_sites->GetMostVisitedURLs(
knn6cf777fc2015-05-06 13:21:48397 base::Bind(&MostVisitedSites::OnMostVisitedURLsAvailable,
398 weak_ptr_factory_.GetWeakPtr()),
[email protected]7ceb4e32013-12-06 04:13:04399 false);
400}
401
[email protected]d4975242014-06-02 18:16:20402void MostVisitedSites::OnMostVisitedURLsAvailable(
[email protected]d4975242014-06-02 18:16:20403 const history::MostVisitedURLList& visited_list) {
knncbbd754d2015-09-09 15:43:40404 ScopedVector<Suggestion> suggestions;
405 size_t num_tiles =
406 std::min(visited_list.size(), static_cast<size_t>(num_sites_));
407 for (size_t i = 0; i < num_tiles; ++i) {
knn6cf777fc2015-05-06 13:21:48408 const history::MostVisitedURL& visited = visited_list[i];
409 if (visited.url.is_empty()) {
410 num_tiles = i;
411 break; // This is the signal that there are no more real visited sites.
412 }
knncbbd754d2015-09-09 15:43:40413 suggestions.push_back(
414 new Suggestion(visited.title, visited.url.spec(), TOP_SITES));
knn6cf777fc2015-05-06 13:21:48415 }
[email protected]d4975242014-06-02 18:16:20416
treib783a3372015-08-25 16:24:39417 received_most_visited_sites_ = true;
knn6cf777fc2015-05-06 13:21:48418 mv_source_ = TOP_SITES;
knncbbd754d2015-09-09 15:43:40419 AddPopularSites(&suggestions);
420 NotifyMostVisitedURLsObserver();
[email protected]d4975242014-06-02 18:16:20421}
422
[email protected]bc093e82014-03-04 21:49:11423void MostVisitedSites::OnSuggestionsProfileAvailable(
[email protected]bc093e82014-03-04 21:49:11424 const SuggestionsProfile& suggestions_profile) {
knn6cf777fc2015-05-06 13:21:48425 int num_tiles = suggestions_profile.suggestions_size();
mathpd91e8eb2015-03-24 17:37:47426 // With no server suggestions, fall back to local Most Visited.
knn6cf777fc2015-05-06 13:21:48427 if (num_tiles == 0) {
[email protected]bc093e82014-03-04 21:49:11428 InitiateTopSitesQuery();
429 return;
430 }
knn6cf777fc2015-05-06 13:21:48431 if (num_sites_ < num_tiles)
432 num_tiles = num_sites_;
[email protected]bc093e82014-03-04 21:49:11433
knncbbd754d2015-09-09 15:43:40434 ScopedVector<Suggestion> suggestions;
knn6cf777fc2015-05-06 13:21:48435 for (int i = 0; i < num_tiles; ++i) {
[email protected]bc093e82014-03-04 21:49:11436 const ChromeSuggestion& suggestion = suggestions_profile.suggestions(i);
knncbbd754d2015-09-09 15:43:40437 suggestions.push_back(new Suggestion(
438 base::UTF8ToUTF16(suggestion.title()), suggestion.url(),
439 SUGGESTIONS_SERVICE,
440 suggestion.providers_size() > 0 ? suggestion.providers(0) : -1));
[email protected]bc093e82014-03-04 21:49:11441 }
treib783a3372015-08-25 16:24:39442
443 received_most_visited_sites_ = true;
[email protected]d4975242014-06-02 18:16:20444 mv_source_ = SUGGESTIONS_SERVICE;
knncbbd754d2015-09-09 15:43:40445 AddPopularSites(&suggestions);
446 NotifyMostVisitedURLsObserver();
knn6cf777fc2015-05-06 13:21:48447}
[email protected]d4975242014-06-02 18:16:20448
knncbbd754d2015-09-09 15:43:40449void MostVisitedSites::AddPopularSites(
450 ScopedVector<Suggestion>* personal_suggestions) {
451 size_t num_personal_suggestions = personal_suggestions->size();
452 DCHECK_LE(num_personal_suggestions, static_cast<size_t>(num_sites_));
treibcffa6502015-08-06 09:12:27453
knncbbd754d2015-09-09 15:43:40454 // Collect non-blacklisted popular suggestions, skipping those already present
455 // in the personal suggestions.
456 size_t num_popular_suggestions = num_sites_ - num_personal_suggestions;
457 ScopedVector<Suggestion> popular_suggestions;
458 popular_suggestions.reserve(num_popular_suggestions);
treibcffa6502015-08-06 09:12:27459
knncbbd754d2015-09-09 15:43:40460 if (num_popular_suggestions > 0 && popular_sites_) {
461 std::set<std::string> personal_hosts;
462 for (const Suggestion* suggestion : *personal_suggestions)
463 personal_hosts.insert(suggestion->url.host());
464 scoped_refptr<TopSites> top_sites(TopSitesFactory::GetForProfile(profile_));
465 for (const PopularSites::Site& popular_site : popular_sites_->sites()) {
466 // Skip blacklisted sites.
467 if (top_sites && top_sites->IsBlacklisted(popular_site.url))
468 continue;
469 std::string host = popular_site.url.host();
470 // Skip suggestions already present in personal.
471 if (personal_hosts.find(host) != personal_hosts.end())
472 continue;
treibcffa6502015-08-06 09:12:27473
knncbbd754d2015-09-09 15:43:40474 popular_suggestions.push_back(
475 new Suggestion(popular_site.title, popular_site.url, POPULAR));
476 if (popular_suggestions.size() >= num_popular_suggestions)
477 break;
478 }
treibcffa6502015-08-06 09:12:27479 }
knncbbd754d2015-09-09 15:43:40480 num_popular_suggestions = popular_suggestions.size();
481 size_t num_actual_tiles = num_personal_suggestions + num_popular_suggestions;
482 std::vector<std::string> old_sites_url;
483 std::vector<bool> old_sites_is_personal;
484 GetPreviousNTPSites(num_actual_tiles, &old_sites_url, &old_sites_is_personal);
485 ScopedVector<Suggestion> merged_suggestions =
486 MergeSuggestions(personal_suggestions, &popular_suggestions,
487 old_sites_url, old_sites_is_personal);
488 DCHECK_EQ(num_actual_tiles, merged_suggestions.size());
489 current_suggestions_.swap(merged_suggestions);
490 if (received_popular_sites_)
491 SaveCurrentNTPSites();
treibcf674e62015-08-17 15:43:01492}
493
494// static
knncbbd754d2015-09-09 15:43:40495ScopedVector<MostVisitedSites::Suggestion> MostVisitedSites::MergeSuggestions(
496 ScopedVector<Suggestion>* personal_suggestions,
497 ScopedVector<Suggestion>* popular_suggestions,
498 const std::vector<std::string>& old_sites_url,
499 const std::vector<bool>& old_sites_is_personal) {
500 size_t num_personal_suggestions = personal_suggestions->size();
501 size_t num_popular_suggestions = popular_suggestions->size();
502 size_t num_tiles = num_popular_suggestions + num_personal_suggestions;
503 ScopedVector<Suggestion> merged_suggestions;
504 merged_suggestions.resize(num_tiles);
treibcf674e62015-08-17 15:43:01505
knncbbd754d2015-09-09 15:43:40506 size_t num_old_tiles = old_sites_url.size();
507 DCHECK_LE(num_old_tiles, num_tiles);
508 DCHECK_EQ(num_old_tiles, old_sites_is_personal.size());
509 std::vector<std::string> old_sites_host;
510 old_sites_host.reserve(num_old_tiles);
511 // Only populate the hosts for popular suggestions as only they can be
512 // replaced by host. Personal suggestions require an exact url match to be
513 // replaced.
514 for (size_t i = 0; i < num_old_tiles; ++i) {
515 old_sites_host.push_back(old_sites_is_personal[i]
516 ? std::string()
517 : GURL(old_sites_url[i]).host());
treibcf674e62015-08-17 15:43:01518 }
519
knncbbd754d2015-09-09 15:43:40520 // Insert personal suggestions if they existed previously.
521 std::vector<size_t> new_personal_suggestions = InsertMatchingSuggestions(
522 personal_suggestions, &merged_suggestions, old_sites_url, old_sites_host);
523 // Insert popular suggestions if they existed previously.
524 std::vector<size_t> new_popular_suggestions = InsertMatchingSuggestions(
525 popular_suggestions, &merged_suggestions, old_sites_url, old_sites_host);
526 // Insert leftover personal suggestions.
527 size_t filled_so_far = InsertAllSuggestions(
528 0, new_personal_suggestions, personal_suggestions, &merged_suggestions);
529 // Insert leftover popular suggestions.
530 InsertAllSuggestions(filled_so_far, new_popular_suggestions,
531 popular_suggestions, &merged_suggestions);
532 return merged_suggestions.Pass();
treibcffa6502015-08-06 09:12:27533}
534
knncbbd754d2015-09-09 15:43:40535void MostVisitedSites::GetPreviousNTPSites(
536 size_t num_tiles,
537 std::vector<std::string>* old_sites_url,
538 std::vector<bool>* old_sites_is_personal) const {
539 const PrefService* prefs = profile_->GetPrefs();
540 const base::ListValue* url_list = prefs->GetList(prefs::kNTPSuggestionsURL);
541 const base::ListValue* source_list =
542 prefs->GetList(prefs::kNTPSuggestionsIsPersonal);
543 DCHECK_EQ(url_list->GetSize(), source_list->GetSize());
544 if (url_list->GetSize() < num_tiles)
545 num_tiles = url_list->GetSize();
546 if (num_tiles == 0) {
547 // No fallback required as Personal suggestions take precedence anyway.
548 return;
549 }
550 old_sites_url->reserve(num_tiles);
551 old_sites_is_personal->reserve(num_tiles);
552 for (size_t i = 0; i < num_tiles; ++i) {
553 std::string url_string;
554 bool success = url_list->GetString(i, &url_string);
555 DCHECK(success);
556 old_sites_url->push_back(url_string);
557 bool is_personal;
558 success = source_list->GetBoolean(i, &is_personal);
559 DCHECK(success);
560 old_sites_is_personal->push_back(is_personal);
561 }
562}
563
564void MostVisitedSites::SaveCurrentNTPSites() {
565 base::ListValue url_list;
566 base::ListValue source_list;
567 for (const Suggestion* suggestion : current_suggestions_) {
568 url_list.AppendString(suggestion->url.spec());
569 source_list.AppendBoolean(suggestion->source != MostVisitedSites::POPULAR);
570 }
571 PrefService* prefs = profile_->GetPrefs();
572 prefs->Set(prefs::kNTPSuggestionsIsPersonal, source_list);
573 prefs->Set(prefs::kNTPSuggestionsURL, url_list);
574}
575
576// static
577std::vector<size_t> MostVisitedSites::InsertMatchingSuggestions(
578 ScopedVector<Suggestion>* src_suggestions,
579 ScopedVector<Suggestion>* dst_suggestions,
580 const std::vector<std::string>& match_urls,
581 const std::vector<std::string>& match_hosts) {
582 std::vector<size_t> unmatched_suggestions;
583 size_t num_src_suggestions = src_suggestions->size();
584 size_t num_matchers = match_urls.size();
585 for (size_t i = 0; i < num_src_suggestions; ++i) {
586 size_t position;
587 for (position = 0; position < num_matchers; ++position) {
588 if ((*dst_suggestions)[position] != nullptr)
589 continue;
590 if (match_urls[position] == (*src_suggestions)[i]->url.spec())
591 break;
592 // match_hosts is only populated for suggestions which can be replaced by
593 // host matching like popular suggestions.
594 if (match_hosts[position] == (*src_suggestions)[i]->url.host())
595 break;
596 }
597 if (position == num_matchers) {
598 unmatched_suggestions.push_back(i);
599 } else {
600 // A move is required as the source and destination containers own the
601 // elements.
602 std::swap((*dst_suggestions)[position], (*src_suggestions)[i]);
603 }
604 }
605 return unmatched_suggestions;
606}
607
608// static
609size_t MostVisitedSites::InsertAllSuggestions(
610 size_t start_position,
611 const std::vector<size_t>& insert_positions,
612 ScopedVector<Suggestion>* src_suggestions,
613 ScopedVector<Suggestion>* dst_suggestions) {
614 size_t num_inserts = insert_positions.size();
615 size_t num_dests = dst_suggestions->size();
616
617 size_t src_pos = 0;
618 size_t i = start_position;
619 for (; i < num_dests && src_pos < num_inserts; ++i) {
620 if ((*dst_suggestions)[i] != nullptr)
621 continue;
622 size_t src = insert_positions[src_pos++];
623 std::swap((*dst_suggestions)[i], (*src_suggestions)[src]);
624 }
625 // Return destination postions filled so far which becomes the start_position
626 // for future runs.
627 return i;
628}
629
630void MostVisitedSites::NotifyMostVisitedURLsObserver() {
631 size_t num_suggestions = current_suggestions_.size();
treib783a3372015-08-25 16:24:39632 if (received_most_visited_sites_ && received_popular_sites_ &&
633 !recorded_uma_) {
634 RecordImpressionUMAMetrics();
knncbbd754d2015-09-09 15:43:40635 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName, num_suggestions);
treib783a3372015-08-25 16:24:39636 recorded_uma_ = true;
637 }
knncbbd754d2015-09-09 15:43:40638 std::vector<base::string16> titles;
639 std::vector<std::string> urls;
640 titles.reserve(num_suggestions);
641 urls.reserve(num_suggestions);
642 for (const Suggestion* suggestion : current_suggestions_) {
643 titles.push_back(suggestion->title);
644 urls.push_back(suggestion->url.spec());
645 }
[email protected]bc093e82014-03-04 21:49:11646 JNIEnv* env = AttachCurrentThread();
knncbbd754d2015-09-09 15:43:40647 DCHECK_EQ(titles.size(), urls.size());
[email protected]bc093e82014-03-04 21:49:11648 Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
knn6cf777fc2015-05-06 13:21:48649 env, observer_.obj(), ToJavaArrayOfStrings(env, titles).obj(),
[email protected]bc093e82014-03-04 21:49:11650 ToJavaArrayOfStrings(env, urls).obj());
651}
652
treibcffa6502015-08-06 09:12:27653void MostVisitedSites::OnPopularSitesAvailable(bool success) {
treib783a3372015-08-25 16:24:39654 received_popular_sites_ = true;
655
treibcffa6502015-08-06 09:12:27656 if (!success) {
657 LOG(WARNING) << "Download of popular sites failed";
658 return;
659 }
660
treib7f5cf872015-08-11 13:11:47661 if (observer_.is_null())
662 return;
663
664 std::vector<std::string> urls;
665 std::vector<std::string> favicon_urls;
666 for (const PopularSites::Site& popular_site : popular_sites_->sites()) {
667 urls.push_back(popular_site.url.spec());
668 favicon_urls.push_back(popular_site.favicon_url.spec());
669 }
670 JNIEnv* env = AttachCurrentThread();
671 Java_MostVisitedURLsObserver_onPopularURLsAvailable(
672 env, observer_.obj(), ToJavaArrayOfStrings(env, urls).obj(),
673 ToJavaArrayOfStrings(env, favicon_urls).obj());
674
675 QueryMostVisitedURLs();
treibcffa6502015-08-06 09:12:27676}
677
treib783a3372015-08-25 16:24:39678void MostVisitedSites::RecordThumbnailUMAMetrics() {
[email protected]32809e9e2014-06-19 01:10:58679 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumLocalThumbnailTilesHistogramName,
680 num_local_thumbs_);
[email protected]1d778e12014-06-17 02:28:51681 num_local_thumbs_ = 0;
[email protected]32809e9e2014-06-19 01:10:58682 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumEmptyTilesHistogramName, num_empty_thumbs_);
[email protected]1d778e12014-06-17 02:28:51683 num_empty_thumbs_ = 0;
[email protected]32809e9e2014-06-19 01:10:58684 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumServerTilesHistogramName, num_server_thumbs_);
[email protected]1d778e12014-06-17 02:28:51685 num_server_thumbs_ = 0;
686}
687
treib783a3372015-08-25 16:24:39688void MostVisitedSites::RecordImpressionUMAMetrics() {
knncbbd754d2015-09-09 15:43:40689 for (size_t i = 0; i < current_suggestions_.size(); i++) {
690 std::string histogram = base::StringPrintf(
691 kImpressionHistogramFormat,
692 current_suggestions_[i]->GetSourceHistogramName().c_str());
treib783a3372015-08-25 16:24:39693 LogHistogramEvent(histogram, static_cast<int>(i), num_sites_);
694 }
695}
696
sdefresneedf9e01f2015-01-13 19:45:41697void MostVisitedSites::TopSitesLoaded(history::TopSites* top_sites) {
698}
699
fserbdb575112015-06-29 21:31:59700void MostVisitedSites::TopSitesChanged(history::TopSites* top_sites,
701 ChangeReason change_reason) {
sdefresneedf9e01f2015-01-13 19:45:41702 if (mv_source_ == TOP_SITES) {
703 // The displayed suggestions are invalidated.
704 QueryMostVisitedURLs();
705 }
706}
707
torne89cc5d92015-09-04 11:16:35708static jlong Init(JNIEnv* env,
709 const JavaParamRef<jobject>& obj,
710 const JavaParamRef<jobject>& jprofile) {
[email protected]7ceb4e32013-12-06 04:13:04711 MostVisitedSites* most_visited_sites =
712 new MostVisitedSites(ProfileAndroid::FromProfileAndroid(jprofile));
713 return reinterpret_cast<intptr_t>(most_visited_sites);
714}