| OLD | NEW |
| (Empty) |
| 1 // 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 <utility> | |
| 8 | |
| 9 #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" | |
| 13 #include "base/callback.h" | |
| 14 #include "base/command_line.h" | |
| 15 #include "base/metrics/field_trial.h" | |
| 16 #include "base/metrics/histogram.h" | |
| 17 #include "base/metrics/sparse_histogram.h" | |
| 18 #include "base/strings/string_number_conversions.h" | |
| 19 #include "base/strings/string_util.h" | |
| 20 #include "base/strings/stringprintf.h" | |
| 21 #include "base/strings/utf_string_conversions.h" | |
| 22 #include "base/time/time.h" | |
| 23 #include "chrome/browser/android/popular_sites.h" | |
| 24 #include "chrome/browser/history/top_sites_factory.h" | |
| 25 #include "chrome/browser/profiles/profile.h" | |
| 26 #include "chrome/browser/profiles/profile_android.h" | |
| 27 #include "chrome/browser/search/suggestions/suggestions_service_factory.h" | |
| 28 #include "chrome/browser/search/suggestions/suggestions_source.h" | |
| 29 #include "chrome/browser/supervised_user/supervised_user_service.h" | |
| 30 #include "chrome/browser/supervised_user/supervised_user_service_factory.h" | |
| 31 #include "chrome/browser/supervised_user/supervised_user_url_filter.h" | |
| 32 #include "chrome/browser/thumbnails/thumbnail_list_source.h" | |
| 33 #include "chrome/common/chrome_switches.h" | |
| 34 #include "chrome/common/pref_names.h" | |
| 35 #include "components/history/core/browser/top_sites.h" | |
| 36 #include "components/pref_registry/pref_registry_syncable.h" | |
| 37 #include "components/prefs/pref_service.h" | |
| 38 #include "components/variations/variations_associated_data.h" | |
| 39 #include "content/public/browser/browser_thread.h" | |
| 40 #include "content/public/browser/url_data_source.h" | |
| 41 #include "jni/MostVisitedSites_jni.h" | |
| 42 #include "third_party/skia/include/core/SkBitmap.h" | |
| 43 #include "ui/gfx/android/java_bitmap.h" | |
| 44 #include "ui/gfx/codec/jpeg_codec.h" | |
| 45 #include "url/gurl.h" | |
| 46 | |
| 47 using base::android::AttachCurrentThread; | |
| 48 using base::android::ConvertJavaStringToUTF8; | |
| 49 using base::android::ScopedJavaGlobalRef; | |
| 50 using base::android::ScopedJavaLocalRef; | |
| 51 using base::android::ToJavaArrayOfStrings; | |
| 52 using content::BrowserThread; | |
| 53 using history::TopSites; | |
| 54 using suggestions::ChromeSuggestion; | |
| 55 using suggestions::SuggestionsProfile; | |
| 56 using suggestions::SuggestionsService; | |
| 57 using suggestions::SuggestionsServiceFactory; | |
| 58 | |
| 59 namespace { | |
| 60 | |
| 61 // Identifiers for the various tile sources. | |
| 62 const char kHistogramClientName[] = "client"; | |
| 63 const char kHistogramServerName[] = "server"; | |
| 64 const char kHistogramServerFormat[] = "server%d"; | |
| 65 const char kHistogramPopularName[] = "popular"; | |
| 66 const char kHistogramWhitelistName[] = "whitelist"; | |
| 67 | |
| 68 const char kPopularSitesFieldTrialName[] = "NTPPopularSites"; | |
| 69 | |
| 70 // The visual type of a most visited tile. | |
| 71 // | |
| 72 // These values must stay in sync with the MostVisitedTileType enum | |
| 73 // in histograms.xml. | |
| 74 // | |
| 75 // A Java counterpart will be generated for this enum. | |
| 76 // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.ntp | |
| 77 enum MostVisitedTileType { | |
| 78 // The icon or thumbnail hasn't loaded yet. | |
| 79 NONE, | |
| 80 // The item displays a site's actual favicon or touch icon. | |
| 81 ICON_REAL, | |
| 82 // The item displays a color derived from the site's favicon or touch icon. | |
| 83 ICON_COLOR, | |
| 84 // The item displays a default gray box in place of an icon. | |
| 85 ICON_DEFAULT, | |
| 86 NUM_TILE_TYPES, | |
| 87 }; | |
| 88 | |
| 89 scoped_ptr<SkBitmap> MaybeFetchLocalThumbnail( | |
| 90 const GURL& url, | |
| 91 const scoped_refptr<TopSites>& top_sites) { | |
| 92 DCHECK_CURRENTLY_ON(BrowserThread::DB); | |
| 93 scoped_refptr<base::RefCountedMemory> image; | |
| 94 scoped_ptr<SkBitmap> bitmap; | |
| 95 if (top_sites && top_sites->GetPageThumbnail(url, false, &image)) | |
| 96 bitmap.reset(gfx::JPEGCodec::Decode(image->front(), image->size())); | |
| 97 return bitmap; | |
| 98 } | |
| 99 | |
| 100 // Log an event for a given |histogram| at a given element |position|. This | |
| 101 // routine exists because regular histogram macros are cached thus can't be used | |
| 102 // if the name of the histogram will change at a given call site. | |
| 103 void LogHistogramEvent(const std::string& histogram, | |
| 104 int position, | |
| 105 int num_sites) { | |
| 106 base::HistogramBase* counter = base::LinearHistogram::FactoryGet( | |
| 107 histogram, | |
| 108 1, | |
| 109 num_sites, | |
| 110 num_sites + 1, | |
| 111 base::Histogram::kUmaTargetedHistogramFlag); | |
| 112 if (counter) | |
| 113 counter->Add(position); | |
| 114 } | |
| 115 | |
| 116 bool ShouldShowPopularSites() { | |
| 117 // Note: It's important to query the field trial state first, to ensure that | |
| 118 // UMA reports the correct group. | |
| 119 const std::string group_name = | |
| 120 base::FieldTrialList::FindFullName(kPopularSitesFieldTrialName); | |
| 121 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); | |
| 122 if (cmd_line->HasSwitch(switches::kDisableNTPPopularSites)) | |
| 123 return false; | |
| 124 if (cmd_line->HasSwitch(switches::kEnableNTPPopularSites)) | |
| 125 return true; | |
| 126 return base::StartsWith(group_name, "Enabled", | |
| 127 base::CompareCase::INSENSITIVE_ASCII); | |
| 128 } | |
| 129 | |
| 130 std::string GetPopularSitesCountry() { | |
| 131 return variations::GetVariationParamValue(kPopularSitesFieldTrialName, | |
| 132 "country"); | |
| 133 } | |
| 134 | |
| 135 std::string GetPopularSitesVersion() { | |
| 136 return variations::GetVariationParamValue(kPopularSitesFieldTrialName, | |
| 137 "version"); | |
| 138 } | |
| 139 | |
| 140 // Determine whether we need any popular suggestions to fill up a grid of | |
| 141 // |num_tiles| tiles. | |
| 142 bool NeedPopularSites(const PrefService* prefs, size_t num_tiles) { | |
| 143 const base::ListValue* source_list = | |
| 144 prefs->GetList(prefs::kNTPSuggestionsIsPersonal); | |
| 145 // If there aren't enough previous suggestions to fill the grid, we need | |
| 146 // popular suggestions. | |
| 147 if (source_list->GetSize() < num_tiles) | |
| 148 return true; | |
| 149 // Otherwise, if any of the previous suggestions is not personal, then also | |
| 150 // get popular suggestions. | |
| 151 for (size_t i = 0; i < num_tiles; ++i) { | |
| 152 bool is_personal = false; | |
| 153 if (source_list->GetBoolean(i, &is_personal) && !is_personal) | |
| 154 return true; | |
| 155 } | |
| 156 // The whole grid is already filled with personal suggestions, no point in | |
| 157 // bothering with popular ones. | |
| 158 return false; | |
| 159 } | |
| 160 | |
| 161 bool AreURLsEquivalent(const GURL& url1, const GURL& url2) { | |
| 162 return url1.host() == url2.host() && url1.path() == url2.path(); | |
| 163 } | |
| 164 | |
| 165 } // namespace | |
| 166 | |
| 167 MostVisitedSites::Suggestion::Suggestion() : provider_index(-1) {} | |
| 168 | |
| 169 MostVisitedSites::Suggestion::~Suggestion() {} | |
| 170 | |
| 171 std::string MostVisitedSites::Suggestion::GetSourceHistogramName() const { | |
| 172 switch (source) { | |
| 173 case MostVisitedSites::TOP_SITES: | |
| 174 return kHistogramClientName; | |
| 175 case MostVisitedSites::POPULAR: | |
| 176 return kHistogramPopularName; | |
| 177 case MostVisitedSites::WHITELIST: | |
| 178 return kHistogramWhitelistName; | |
| 179 case MostVisitedSites::SUGGESTIONS_SERVICE: | |
| 180 return provider_index >= 0 | |
| 181 ? base::StringPrintf(kHistogramServerFormat, provider_index) | |
| 182 : kHistogramServerName; | |
| 183 } | |
| 184 NOTREACHED(); | |
| 185 return std::string(); | |
| 186 } | |
| 187 | |
| 188 MostVisitedSites::MostVisitedSites(Profile* profile) | |
| 189 : profile_(profile), num_sites_(0), received_most_visited_sites_(false), | |
| 190 received_popular_sites_(false), recorded_uma_(false), | |
| 191 scoped_observer_(this), weak_ptr_factory_(this) { | |
| 192 // Register the debugging page for the Suggestions Service and the thumbnails | |
| 193 // debugging page. | |
| 194 content::URLDataSource::Add(profile_, | |
| 195 new suggestions::SuggestionsSource(profile_)); | |
| 196 content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_)); | |
| 197 | |
| 198 SuggestionsService* suggestions_service = | |
| 199 SuggestionsServiceFactory::GetForProfile(profile_); | |
| 200 suggestions_subscription_ = suggestions_service->AddCallback( | |
| 201 base::Bind(&MostVisitedSites::OnSuggestionsProfileAvailable, | |
| 202 base::Unretained(this))); | |
| 203 | |
| 204 SupervisedUserService* supervised_user_service = | |
| 205 SupervisedUserServiceFactory::GetForProfile(profile_); | |
| 206 supervised_user_service->AddObserver(this); | |
| 207 } | |
| 208 | |
| 209 MostVisitedSites::~MostVisitedSites() { | |
| 210 SupervisedUserService* supervised_user_service = | |
| 211 SupervisedUserServiceFactory::GetForProfile(profile_); | |
| 212 supervised_user_service->RemoveObserver(this); | |
| 213 } | |
| 214 | |
| 215 void MostVisitedSites::Destroy(JNIEnv* env, const JavaParamRef<jobject>& obj) { | |
| 216 delete this; | |
| 217 } | |
| 218 | |
| 219 void MostVisitedSites::SetMostVisitedURLsObserver( | |
| 220 JNIEnv* env, | |
| 221 const JavaParamRef<jobject>& obj, | |
| 222 const JavaParamRef<jobject>& j_observer, | |
| 223 jint num_sites) { | |
| 224 observer_.Reset(env, j_observer); | |
| 225 num_sites_ = num_sites; | |
| 226 | |
| 227 if (ShouldShowPopularSites() && | |
| 228 NeedPopularSites(profile_->GetPrefs(), num_sites_)) { | |
| 229 popular_sites_.reset(new PopularSites( | |
| 230 profile_, | |
| 231 GetPopularSitesCountry(), | |
| 232 GetPopularSitesVersion(), | |
| 233 false, | |
| 234 base::Bind(&MostVisitedSites::OnPopularSitesAvailable, | |
| 235 base::Unretained(this)))); | |
| 236 } else { | |
| 237 received_popular_sites_ = true; | |
| 238 } | |
| 239 | |
| 240 OnSuggestionsProfileAvailable( | |
| 241 SuggestionsServiceFactory::GetForProfile(profile_) | |
| 242 ->GetSuggestionsDataFromCache()); | |
| 243 | |
| 244 QueryMostVisitedURLs(); | |
| 245 | |
| 246 scoped_refptr<TopSites> top_sites = TopSitesFactory::GetForProfile(profile_); | |
| 247 if (top_sites) { | |
| 248 // TopSites updates itself after a delay. To ensure up-to-date results, | |
| 249 // force an update now. | |
| 250 top_sites->SyncWithHistory(); | |
| 251 | |
| 252 // Register as TopSitesObserver so that we can update ourselves when the | |
| 253 // TopSites changes. | |
| 254 scoped_observer_.Add(top_sites.get()); | |
| 255 } | |
| 256 } | |
| 257 | |
| 258 void MostVisitedSites::GetURLThumbnail( | |
| 259 JNIEnv* env, | |
| 260 const JavaParamRef<jobject>& obj, | |
| 261 const JavaParamRef<jstring>& j_url, | |
| 262 const JavaParamRef<jobject>& j_callback_obj) { | |
| 263 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 264 scoped_ptr<ScopedJavaGlobalRef<jobject>> j_callback( | |
| 265 new ScopedJavaGlobalRef<jobject>()); | |
| 266 j_callback->Reset(env, j_callback_obj); | |
| 267 | |
| 268 GURL url(ConvertJavaStringToUTF8(env, j_url)); | |
| 269 scoped_refptr<TopSites> top_sites(TopSitesFactory::GetForProfile(profile_)); | |
| 270 | |
| 271 BrowserThread::PostTaskAndReplyWithResult( | |
| 272 BrowserThread::DB, FROM_HERE, | |
| 273 base::Bind(&MaybeFetchLocalThumbnail, url, top_sites), | |
| 274 base::Bind(&MostVisitedSites::OnLocalThumbnailFetched, | |
| 275 weak_ptr_factory_.GetWeakPtr(), url, | |
| 276 base::Passed(&j_callback))); | |
| 277 } | |
| 278 | |
| 279 void MostVisitedSites::OnLocalThumbnailFetched( | |
| 280 const GURL& url, | |
| 281 scoped_ptr<ScopedJavaGlobalRef<jobject>> j_callback, | |
| 282 scoped_ptr<SkBitmap> bitmap) { | |
| 283 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 284 if (!bitmap.get()) { | |
| 285 // A thumbnail is not locally available for |url|. Make sure it is put in | |
| 286 // the list to be fetched at the next visit to this site. | |
| 287 scoped_refptr<TopSites> top_sites(TopSitesFactory::GetForProfile(profile_)); | |
| 288 if (top_sites) | |
| 289 top_sites->AddForcedURL(url, base::Time::Now()); | |
| 290 // Also fetch a remote thumbnail if possible. PopularSites or the | |
| 291 // SuggestionsService can supply a thumbnail download URL. | |
| 292 SuggestionsService* suggestions_service = | |
| 293 SuggestionsServiceFactory::GetForProfile(profile_); | |
| 294 if (popular_sites_) { | |
| 295 const std::vector<PopularSites::Site>& sites = popular_sites_->sites(); | |
| 296 auto it = std::find_if( | |
| 297 sites.begin(), sites.end(), | |
| 298 [&url](const PopularSites::Site& site) { return site.url == url; }); | |
| 299 if (it != sites.end() && it->thumbnail_url.is_valid()) { | |
| 300 return suggestions_service->GetPageThumbnailWithURL( | |
| 301 url, it->thumbnail_url, | |
| 302 base::Bind(&MostVisitedSites::OnObtainedThumbnail, | |
| 303 weak_ptr_factory_.GetWeakPtr(), false, | |
| 304 base::Passed(&j_callback))); | |
| 305 } | |
| 306 } | |
| 307 if (mv_source_ == SUGGESTIONS_SERVICE) { | |
| 308 return suggestions_service->GetPageThumbnail( | |
| 309 url, base::Bind(&MostVisitedSites::OnObtainedThumbnail, | |
| 310 weak_ptr_factory_.GetWeakPtr(), false, | |
| 311 base::Passed(&j_callback))); | |
| 312 } | |
| 313 } | |
| 314 OnObtainedThumbnail(true, std::move(j_callback), url, bitmap.get()); | |
| 315 } | |
| 316 | |
| 317 void MostVisitedSites::OnObtainedThumbnail( | |
| 318 bool is_local_thumbnail, | |
| 319 scoped_ptr<ScopedJavaGlobalRef<jobject>> j_callback, | |
| 320 const GURL& url, | |
| 321 const SkBitmap* bitmap) { | |
| 322 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 323 JNIEnv* env = AttachCurrentThread(); | |
| 324 ScopedJavaLocalRef<jobject> j_bitmap; | |
| 325 if (bitmap) | |
| 326 j_bitmap = gfx::ConvertToJavaBitmap(bitmap); | |
| 327 Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable( | |
| 328 env, j_callback->obj(), j_bitmap.obj(), is_local_thumbnail); | |
| 329 } | |
| 330 | |
| 331 void MostVisitedSites::AddOrRemoveBlacklistedUrl( | |
| 332 JNIEnv* env, | |
| 333 const JavaParamRef<jobject>& obj, | |
| 334 const JavaParamRef<jstring>& j_url, | |
| 335 jboolean add_url) { | |
| 336 GURL url(ConvertJavaStringToUTF8(env, j_url)); | |
| 337 | |
| 338 // Always blacklist in the local TopSites. | |
| 339 scoped_refptr<TopSites> top_sites = TopSitesFactory::GetForProfile(profile_); | |
| 340 if (top_sites) { | |
| 341 if (add_url) | |
| 342 top_sites->AddBlacklistedURL(url); | |
| 343 else | |
| 344 top_sites->RemoveBlacklistedURL(url); | |
| 345 } | |
| 346 | |
| 347 // Only blacklist in the server-side suggestions service if it's active. | |
| 348 if (mv_source_ == SUGGESTIONS_SERVICE) { | |
| 349 SuggestionsService* suggestions_service = | |
| 350 SuggestionsServiceFactory::GetForProfile(profile_); | |
| 351 if (add_url) | |
| 352 suggestions_service->BlacklistURL(url); | |
| 353 else | |
| 354 suggestions_service->UndoBlacklistURL(url); | |
| 355 } | |
| 356 } | |
| 357 | |
| 358 void MostVisitedSites::RecordTileTypeMetrics( | |
| 359 JNIEnv* env, | |
| 360 const JavaParamRef<jobject>& obj, | |
| 361 const JavaParamRef<jintArray>& jtile_types) { | |
| 362 std::vector<int> tile_types; | |
| 363 base::android::JavaIntArrayToIntVector(env, jtile_types, &tile_types); | |
| 364 DCHECK_EQ(current_suggestions_.size(), tile_types.size()); | |
| 365 | |
| 366 int counts_per_type[NUM_TILE_TYPES] = {0}; | |
| 367 for (size_t i = 0; i < tile_types.size(); ++i) { | |
| 368 int tile_type = tile_types[i]; | |
| 369 ++counts_per_type[tile_type]; | |
| 370 std::string histogram = base::StringPrintf( | |
| 371 "NewTabPage.TileType.%s", | |
| 372 current_suggestions_[i]->GetSourceHistogramName().c_str()); | |
| 373 LogHistogramEvent(histogram, tile_type, NUM_TILE_TYPES); | |
| 374 } | |
| 375 | |
| 376 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.IconsReal", | |
| 377 counts_per_type[ICON_REAL]); | |
| 378 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.IconsColor", | |
| 379 counts_per_type[ICON_COLOR]); | |
| 380 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.IconsGray", | |
| 381 counts_per_type[ICON_DEFAULT]); | |
| 382 } | |
| 383 | |
| 384 void MostVisitedSites::RecordOpenedMostVisitedItem( | |
| 385 JNIEnv* env, | |
| 386 const JavaParamRef<jobject>& obj, | |
| 387 jint index, | |
| 388 jint tile_type) { | |
| 389 DCHECK_GE(index, 0); | |
| 390 DCHECK_LT(index, static_cast<int>(current_suggestions_.size())); | |
| 391 std::string histogram = base::StringPrintf( | |
| 392 "NewTabPage.MostVisited.%s", | |
| 393 current_suggestions_[index]->GetSourceHistogramName().c_str()); | |
| 394 LogHistogramEvent(histogram, index, num_sites_); | |
| 395 | |
| 396 histogram = base::StringPrintf( | |
| 397 "NewTabPage.TileTypeClicked.%s", | |
| 398 current_suggestions_[index]->GetSourceHistogramName().c_str()); | |
| 399 LogHistogramEvent(histogram, tile_type, NUM_TILE_TYPES); | |
| 400 } | |
| 401 | |
| 402 void MostVisitedSites::OnURLFilterChanged() { | |
| 403 QueryMostVisitedURLs(); | |
| 404 } | |
| 405 | |
| 406 // static | |
| 407 bool MostVisitedSites::Register(JNIEnv* env) { | |
| 408 return RegisterNativesImpl(env); | |
| 409 } | |
| 410 | |
| 411 // static | |
| 412 void MostVisitedSites::RegisterProfilePrefs( | |
| 413 user_prefs::PrefRegistrySyncable* registry) { | |
| 414 registry->RegisterListPref(prefs::kNTPSuggestionsURL); | |
| 415 registry->RegisterListPref(prefs::kNTPSuggestionsIsPersonal); | |
| 416 } | |
| 417 | |
| 418 void MostVisitedSites::QueryMostVisitedURLs() { | |
| 419 SuggestionsService* suggestions_service = | |
| 420 SuggestionsServiceFactory::GetForProfile(profile_); | |
| 421 if (!suggestions_service->FetchSuggestionsData()) | |
| 422 InitiateTopSitesQuery(); | |
| 423 } | |
| 424 | |
| 425 void MostVisitedSites::InitiateTopSitesQuery() { | |
| 426 scoped_refptr<TopSites> top_sites = TopSitesFactory::GetForProfile(profile_); | |
| 427 if (!top_sites) | |
| 428 return; | |
| 429 | |
| 430 top_sites->GetMostVisitedURLs( | |
| 431 base::Bind(&MostVisitedSites::OnMostVisitedURLsAvailable, | |
| 432 weak_ptr_factory_.GetWeakPtr()), | |
| 433 false); | |
| 434 } | |
| 435 | |
| 436 base::FilePath MostVisitedSites::GetWhitelistLargeIconPath(const GURL& url) { | |
| 437 SupervisedUserService* supervised_user_service = | |
| 438 SupervisedUserServiceFactory::GetForProfile(profile_); | |
| 439 | |
| 440 for (const auto& whitelist : supervised_user_service->whitelists()) { | |
| 441 if (AreURLsEquivalent(whitelist->entry_point(), url)) | |
| 442 return whitelist->large_icon_path(); | |
| 443 } | |
| 444 return base::FilePath(); | |
| 445 } | |
| 446 | |
| 447 void MostVisitedSites::OnMostVisitedURLsAvailable( | |
| 448 const history::MostVisitedURLList& visited_list) { | |
| 449 SupervisedUserURLFilter* url_filter = | |
| 450 SupervisedUserServiceFactory::GetForProfile(profile_) | |
| 451 ->GetURLFilterForUIThread(); | |
| 452 | |
| 453 MostVisitedSites::SuggestionsVector suggestions; | |
| 454 size_t num_tiles = | |
| 455 std::min(visited_list.size(), static_cast<size_t>(num_sites_)); | |
| 456 for (size_t i = 0; i < num_tiles; ++i) { | |
| 457 const history::MostVisitedURL& visited = visited_list[i]; | |
| 458 if (visited.url.is_empty()) { | |
| 459 num_tiles = i; | |
| 460 break; // This is the signal that there are no more real visited sites. | |
| 461 } | |
| 462 if (url_filter->GetFilteringBehaviorForURL(visited.url) == | |
| 463 SupervisedUserURLFilter::FilteringBehavior::BLOCK) { | |
| 464 continue; | |
| 465 } | |
| 466 | |
| 467 scoped_ptr<Suggestion> suggestion(new Suggestion()); | |
| 468 suggestion->title = visited.title; | |
| 469 suggestion->url = visited.url; | |
| 470 suggestion->source = TOP_SITES; | |
| 471 suggestion->whitelist_icon_path = GetWhitelistLargeIconPath(visited.url); | |
| 472 | |
| 473 suggestions.push_back(std::move(suggestion)); | |
| 474 } | |
| 475 | |
| 476 received_most_visited_sites_ = true; | |
| 477 mv_source_ = TOP_SITES; | |
| 478 SaveNewNTPSuggestions(&suggestions); | |
| 479 NotifyMostVisitedURLsObserver(); | |
| 480 } | |
| 481 | |
| 482 void MostVisitedSites::OnSuggestionsProfileAvailable( | |
| 483 const SuggestionsProfile& suggestions_profile) { | |
| 484 int num_tiles = suggestions_profile.suggestions_size(); | |
| 485 // With no server suggestions, fall back to local Most Visited. | |
| 486 if (num_tiles == 0) { | |
| 487 InitiateTopSitesQuery(); | |
| 488 return; | |
| 489 } | |
| 490 if (num_sites_ < num_tiles) | |
| 491 num_tiles = num_sites_; | |
| 492 | |
| 493 SupervisedUserURLFilter* url_filter = | |
| 494 SupervisedUserServiceFactory::GetForProfile(profile_) | |
| 495 ->GetURLFilterForUIThread(); | |
| 496 MostVisitedSites::SuggestionsVector suggestions; | |
| 497 for (int i = 0; i < num_tiles; ++i) { | |
| 498 const ChromeSuggestion& suggestion = suggestions_profile.suggestions(i); | |
| 499 if (url_filter->GetFilteringBehaviorForURL(GURL(suggestion.url())) == | |
| 500 SupervisedUserURLFilter::FilteringBehavior::BLOCK) { | |
| 501 continue; | |
| 502 } | |
| 503 | |
| 504 scoped_ptr<Suggestion> generated_suggestion(new Suggestion()); | |
| 505 generated_suggestion->title = base::UTF8ToUTF16(suggestion.title()); | |
| 506 generated_suggestion->url = GURL(suggestion.url()); | |
| 507 generated_suggestion->source = SUGGESTIONS_SERVICE; | |
| 508 generated_suggestion->whitelist_icon_path = GetWhitelistLargeIconPath( | |
| 509 GURL(suggestion.url())); | |
| 510 if (suggestion.providers_size() > 0) | |
| 511 generated_suggestion->provider_index = suggestion.providers(0); | |
| 512 | |
| 513 suggestions.push_back(std::move(generated_suggestion)); | |
| 514 } | |
| 515 | |
| 516 received_most_visited_sites_ = true; | |
| 517 mv_source_ = SUGGESTIONS_SERVICE; | |
| 518 SaveNewNTPSuggestions(&suggestions); | |
| 519 NotifyMostVisitedURLsObserver(); | |
| 520 } | |
| 521 | |
| 522 MostVisitedSites::SuggestionsVector | |
| 523 MostVisitedSites::CreateWhitelistEntryPointSuggestions( | |
| 524 const MostVisitedSites::SuggestionsVector& personal_suggestions) { | |
| 525 size_t num_personal_suggestions = personal_suggestions.size(); | |
| 526 DCHECK_LE(num_personal_suggestions, static_cast<size_t>(num_sites_)); | |
| 527 | |
| 528 size_t num_whitelist_suggestions = num_sites_ - num_personal_suggestions; | |
| 529 MostVisitedSites::SuggestionsVector whitelist_suggestions; | |
| 530 | |
| 531 SupervisedUserService* supervised_user_service = | |
| 532 SupervisedUserServiceFactory::GetForProfile(profile_); | |
| 533 SupervisedUserURLFilter* url_filter = | |
| 534 supervised_user_service->GetURLFilterForUIThread(); | |
| 535 | |
| 536 std::set<std::string> personal_hosts; | |
| 537 for (const auto& suggestion : personal_suggestions) | |
| 538 personal_hosts.insert(suggestion->url.host()); | |
| 539 scoped_refptr<TopSites> top_sites(TopSitesFactory::GetForProfile(profile_)); | |
| 540 | |
| 541 for (const auto& whitelist : supervised_user_service->whitelists()) { | |
| 542 // Skip blacklisted sites. | |
| 543 if (top_sites && top_sites->IsBlacklisted(whitelist->entry_point())) | |
| 544 continue; | |
| 545 | |
| 546 // Skip suggestions already present. | |
| 547 if (personal_hosts.find(whitelist->entry_point().host()) != | |
| 548 personal_hosts.end()) | |
| 549 continue; | |
| 550 | |
| 551 // Skip whitelist entry points that are manually blocked. | |
| 552 if (url_filter->GetFilteringBehaviorForURL(whitelist->entry_point()) == | |
| 553 SupervisedUserURLFilter::FilteringBehavior::BLOCK) { | |
| 554 continue; | |
| 555 } | |
| 556 | |
| 557 scoped_ptr<Suggestion> suggestion(new Suggestion()); | |
| 558 suggestion->title = whitelist->title(); | |
| 559 suggestion->url = whitelist->entry_point(); | |
| 560 suggestion->source = WHITELIST; | |
| 561 suggestion->whitelist_icon_path = whitelist->large_icon_path(); | |
| 562 | |
| 563 whitelist_suggestions.push_back(std::move(suggestion)); | |
| 564 if (whitelist_suggestions.size() >= num_whitelist_suggestions) | |
| 565 break; | |
| 566 } | |
| 567 | |
| 568 return whitelist_suggestions; | |
| 569 } | |
| 570 | |
| 571 MostVisitedSites::SuggestionsVector | |
| 572 MostVisitedSites::CreatePopularSitesSuggestions( | |
| 573 const MostVisitedSites::SuggestionsVector& personal_suggestions, | |
| 574 const MostVisitedSites::SuggestionsVector& whitelist_suggestions) { | |
| 575 // For child accounts popular sites suggestions will not be added. | |
| 576 if (profile_->IsChild()) | |
| 577 return MostVisitedSites::SuggestionsVector(); | |
| 578 | |
| 579 size_t num_suggestions = | |
| 580 personal_suggestions.size() + whitelist_suggestions.size(); | |
| 581 DCHECK_LE(num_suggestions, static_cast<size_t>(num_sites_)); | |
| 582 | |
| 583 // Collect non-blacklisted popular suggestions, skipping those already present | |
| 584 // in the personal suggestions. | |
| 585 size_t num_popular_sites_suggestions = num_sites_ - num_suggestions; | |
| 586 MostVisitedSites::SuggestionsVector popular_sites_suggestions; | |
| 587 | |
| 588 if (num_popular_sites_suggestions > 0 && popular_sites_) { | |
| 589 std::set<std::string> hosts; | |
| 590 for (const auto& suggestion : personal_suggestions) | |
| 591 hosts.insert(suggestion->url.host()); | |
| 592 for (const auto& suggestion : whitelist_suggestions) | |
| 593 hosts.insert(suggestion->url.host()); | |
| 594 scoped_refptr<TopSites> top_sites(TopSitesFactory::GetForProfile(profile_)); | |
| 595 for (const PopularSites::Site& popular_site : popular_sites_->sites()) { | |
| 596 // Skip blacklisted sites. | |
| 597 if (top_sites && top_sites->IsBlacklisted(popular_site.url)) | |
| 598 continue; | |
| 599 std::string host = popular_site.url.host(); | |
| 600 // Skip suggestions already present in personal or whitelists. | |
| 601 if (hosts.find(host) != hosts.end()) | |
| 602 continue; | |
| 603 | |
| 604 scoped_ptr<Suggestion> suggestion(new Suggestion()); | |
| 605 suggestion->title = popular_site.title; | |
| 606 suggestion->url = GURL(popular_site.url); | |
| 607 suggestion->source = POPULAR; | |
| 608 | |
| 609 popular_sites_suggestions.push_back(std::move(suggestion)); | |
| 610 if (popular_sites_suggestions.size() >= num_popular_sites_suggestions) | |
| 611 break; | |
| 612 } | |
| 613 } | |
| 614 return popular_sites_suggestions; | |
| 615 } | |
| 616 | |
| 617 void MostVisitedSites::SaveNewNTPSuggestions( | |
| 618 MostVisitedSites::SuggestionsVector* personal_suggestions) { | |
| 619 MostVisitedSites::SuggestionsVector whitelist_suggestions = | |
| 620 CreateWhitelistEntryPointSuggestions(*personal_suggestions); | |
| 621 MostVisitedSites::SuggestionsVector popular_sites_suggestions = | |
| 622 CreatePopularSitesSuggestions(*personal_suggestions, | |
| 623 whitelist_suggestions); | |
| 624 size_t num_actual_tiles = personal_suggestions->size() + | |
| 625 whitelist_suggestions.size() + | |
| 626 popular_sites_suggestions.size(); | |
| 627 std::vector<std::string> old_sites_url; | |
| 628 std::vector<bool> old_sites_is_personal; | |
| 629 GetPreviousNTPSites(num_actual_tiles, &old_sites_url, &old_sites_is_personal); | |
| 630 MostVisitedSites::SuggestionsVector merged_suggestions = MergeSuggestions( | |
| 631 personal_suggestions, &whitelist_suggestions, &popular_sites_suggestions, | |
| 632 old_sites_url, old_sites_is_personal); | |
| 633 DCHECK_EQ(num_actual_tiles, merged_suggestions.size()); | |
| 634 current_suggestions_.swap(merged_suggestions); | |
| 635 if (received_popular_sites_) | |
| 636 SaveCurrentNTPSites(); | |
| 637 } | |
| 638 | |
| 639 // static | |
| 640 MostVisitedSites::SuggestionsVector MostVisitedSites::MergeSuggestions( | |
| 641 MostVisitedSites::SuggestionsVector* personal_suggestions, | |
| 642 MostVisitedSites::SuggestionsVector* whitelist_suggestions, | |
| 643 MostVisitedSites::SuggestionsVector* popular_suggestions, | |
| 644 const std::vector<std::string>& old_sites_url, | |
| 645 const std::vector<bool>& old_sites_is_personal) { | |
| 646 size_t num_personal_suggestions = personal_suggestions->size(); | |
| 647 size_t num_whitelist_suggestions = whitelist_suggestions->size(); | |
| 648 size_t num_popular_suggestions = popular_suggestions->size(); | |
| 649 size_t num_tiles = num_popular_suggestions + num_whitelist_suggestions + | |
| 650 num_personal_suggestions; | |
| 651 MostVisitedSites::SuggestionsVector merged_suggestions; | |
| 652 merged_suggestions.resize(num_tiles); | |
| 653 | |
| 654 size_t num_old_tiles = old_sites_url.size(); | |
| 655 DCHECK_LE(num_old_tiles, num_tiles); | |
| 656 DCHECK_EQ(num_old_tiles, old_sites_is_personal.size()); | |
| 657 std::vector<std::string> old_sites_host; | |
| 658 old_sites_host.reserve(num_old_tiles); | |
| 659 // Only populate the hosts for popular suggestions as only they can be | |
| 660 // replaced by host. Personal suggestions require an exact url match to be | |
| 661 // replaced. | |
| 662 for (size_t i = 0; i < num_old_tiles; ++i) { | |
| 663 old_sites_host.push_back(old_sites_is_personal[i] | |
| 664 ? std::string() | |
| 665 : GURL(old_sites_url[i]).host()); | |
| 666 } | |
| 667 | |
| 668 // Insert personal suggestions if they existed previously. | |
| 669 std::vector<size_t> new_personal_suggestions = InsertMatchingSuggestions( | |
| 670 personal_suggestions, &merged_suggestions, old_sites_url, old_sites_host); | |
| 671 // Insert whitelist suggestions if they existed previously. | |
| 672 std::vector<size_t> new_whitelist_suggestions = | |
| 673 InsertMatchingSuggestions(whitelist_suggestions, &merged_suggestions, | |
| 674 old_sites_url, old_sites_host); | |
| 675 // Insert popular suggestions if they existed previously. | |
| 676 std::vector<size_t> new_popular_suggestions = InsertMatchingSuggestions( | |
| 677 popular_suggestions, &merged_suggestions, old_sites_url, old_sites_host); | |
| 678 // Insert leftover personal suggestions. | |
| 679 size_t filled_so_far = InsertAllSuggestions( | |
| 680 0, new_personal_suggestions, personal_suggestions, &merged_suggestions); | |
| 681 // Insert leftover whitelist suggestions. | |
| 682 filled_so_far = | |
| 683 InsertAllSuggestions(filled_so_far, new_whitelist_suggestions, | |
| 684 whitelist_suggestions, &merged_suggestions); | |
| 685 // Insert leftover popular suggestions. | |
| 686 InsertAllSuggestions(filled_so_far, new_popular_suggestions, | |
| 687 popular_suggestions, &merged_suggestions); | |
| 688 return merged_suggestions; | |
| 689 } | |
| 690 | |
| 691 void MostVisitedSites::GetPreviousNTPSites( | |
| 692 size_t num_tiles, | |
| 693 std::vector<std::string>* old_sites_url, | |
| 694 std::vector<bool>* old_sites_is_personal) const { | |
| 695 const PrefService* prefs = profile_->GetPrefs(); | |
| 696 const base::ListValue* url_list = prefs->GetList(prefs::kNTPSuggestionsURL); | |
| 697 const base::ListValue* source_list = | |
| 698 prefs->GetList(prefs::kNTPSuggestionsIsPersonal); | |
| 699 DCHECK_EQ(url_list->GetSize(), source_list->GetSize()); | |
| 700 if (url_list->GetSize() < num_tiles) | |
| 701 num_tiles = url_list->GetSize(); | |
| 702 if (num_tiles == 0) { | |
| 703 // No fallback required as Personal suggestions take precedence anyway. | |
| 704 return; | |
| 705 } | |
| 706 old_sites_url->reserve(num_tiles); | |
| 707 old_sites_is_personal->reserve(num_tiles); | |
| 708 for (size_t i = 0; i < num_tiles; ++i) { | |
| 709 std::string url_string; | |
| 710 bool success = url_list->GetString(i, &url_string); | |
| 711 DCHECK(success); | |
| 712 old_sites_url->push_back(url_string); | |
| 713 bool is_personal; | |
| 714 success = source_list->GetBoolean(i, &is_personal); | |
| 715 DCHECK(success); | |
| 716 old_sites_is_personal->push_back(is_personal); | |
| 717 } | |
| 718 } | |
| 719 | |
| 720 void MostVisitedSites::SaveCurrentNTPSites() { | |
| 721 base::ListValue url_list; | |
| 722 base::ListValue source_list; | |
| 723 for (const auto& suggestion : current_suggestions_) { | |
| 724 url_list.AppendString(suggestion->url.spec()); | |
| 725 source_list.AppendBoolean(suggestion->source != MostVisitedSites::POPULAR); | |
| 726 } | |
| 727 PrefService* prefs = profile_->GetPrefs(); | |
| 728 prefs->Set(prefs::kNTPSuggestionsIsPersonal, source_list); | |
| 729 prefs->Set(prefs::kNTPSuggestionsURL, url_list); | |
| 730 } | |
| 731 | |
| 732 // static | |
| 733 std::vector<size_t> MostVisitedSites::InsertMatchingSuggestions( | |
| 734 MostVisitedSites::SuggestionsVector* src_suggestions, | |
| 735 MostVisitedSites::SuggestionsVector* dst_suggestions, | |
| 736 const std::vector<std::string>& match_urls, | |
| 737 const std::vector<std::string>& match_hosts) { | |
| 738 std::vector<size_t> unmatched_suggestions; | |
| 739 size_t num_src_suggestions = src_suggestions->size(); | |
| 740 size_t num_matchers = match_urls.size(); | |
| 741 for (size_t i = 0; i < num_src_suggestions; ++i) { | |
| 742 size_t position; | |
| 743 for (position = 0; position < num_matchers; ++position) { | |
| 744 if ((*dst_suggestions)[position] != nullptr) | |
| 745 continue; | |
| 746 if (match_urls[position] == (*src_suggestions)[i]->url.spec()) | |
| 747 break; | |
| 748 // match_hosts is only populated for suggestions which can be replaced by | |
| 749 // host matching like popular suggestions. | |
| 750 if (match_hosts[position] == (*src_suggestions)[i]->url.host()) | |
| 751 break; | |
| 752 } | |
| 753 if (position == num_matchers) { | |
| 754 unmatched_suggestions.push_back(i); | |
| 755 } else { | |
| 756 // A move is required as the source and destination containers own the | |
| 757 // elements. | |
| 758 std::swap((*dst_suggestions)[position], (*src_suggestions)[i]); | |
| 759 } | |
| 760 } | |
| 761 return unmatched_suggestions; | |
| 762 } | |
| 763 | |
| 764 // static | |
| 765 size_t MostVisitedSites::InsertAllSuggestions( | |
| 766 size_t start_position, | |
| 767 const std::vector<size_t>& insert_positions, | |
| 768 std::vector<scoped_ptr<Suggestion>>* src_suggestions, | |
| 769 std::vector<scoped_ptr<Suggestion>>* dst_suggestions) { | |
| 770 size_t num_inserts = insert_positions.size(); | |
| 771 size_t num_dests = dst_suggestions->size(); | |
| 772 | |
| 773 size_t src_pos = 0; | |
| 774 size_t i = start_position; | |
| 775 for (; i < num_dests && src_pos < num_inserts; ++i) { | |
| 776 if ((*dst_suggestions)[i] != nullptr) | |
| 777 continue; | |
| 778 size_t src = insert_positions[src_pos++]; | |
| 779 std::swap((*dst_suggestions)[i], (*src_suggestions)[src]); | |
| 780 } | |
| 781 // Return destination postions filled so far which becomes the start_position | |
| 782 // for future runs. | |
| 783 return i; | |
| 784 } | |
| 785 | |
| 786 void MostVisitedSites::NotifyMostVisitedURLsObserver() { | |
| 787 size_t num_suggestions = current_suggestions_.size(); | |
| 788 if (received_most_visited_sites_ && received_popular_sites_ && | |
| 789 !recorded_uma_) { | |
| 790 RecordImpressionUMAMetrics(); | |
| 791 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.NumberOfTiles", num_suggestions); | |
| 792 recorded_uma_ = true; | |
| 793 } | |
| 794 | |
| 795 if (observer_.is_null()) | |
| 796 return; | |
| 797 | |
| 798 std::vector<base::string16> titles; | |
| 799 std::vector<std::string> urls; | |
| 800 std::vector<std::string> whitelist_icon_paths; | |
| 801 titles.reserve(num_suggestions); | |
| 802 urls.reserve(num_suggestions); | |
| 803 for (const auto& suggestion : current_suggestions_) { | |
| 804 titles.push_back(suggestion->title); | |
| 805 urls.push_back(suggestion->url.spec()); | |
| 806 whitelist_icon_paths.push_back(suggestion->whitelist_icon_path.value()); | |
| 807 } | |
| 808 JNIEnv* env = AttachCurrentThread(); | |
| 809 DCHECK_EQ(titles.size(), urls.size()); | |
| 810 Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable( | |
| 811 env, observer_.obj(), ToJavaArrayOfStrings(env, titles).obj(), | |
| 812 ToJavaArrayOfStrings(env, urls).obj(), | |
| 813 ToJavaArrayOfStrings(env, whitelist_icon_paths).obj()); | |
| 814 } | |
| 815 | |
| 816 void MostVisitedSites::OnPopularSitesAvailable(bool success) { | |
| 817 received_popular_sites_ = true; | |
| 818 | |
| 819 if (!success) { | |
| 820 LOG(WARNING) << "Download of popular sites failed"; | |
| 821 return; | |
| 822 } | |
| 823 | |
| 824 if (observer_.is_null()) | |
| 825 return; | |
| 826 | |
| 827 std::vector<std::string> urls; | |
| 828 std::vector<std::string> favicon_urls; | |
| 829 std::vector<std::string> large_icon_urls; | |
| 830 for (const PopularSites::Site& popular_site : popular_sites_->sites()) { | |
| 831 urls.push_back(popular_site.url.spec()); | |
| 832 favicon_urls.push_back(popular_site.favicon_url.spec()); | |
| 833 large_icon_urls.push_back(popular_site.large_icon_url.spec()); | |
| 834 } | |
| 835 JNIEnv* env = AttachCurrentThread(); | |
| 836 Java_MostVisitedURLsObserver_onPopularURLsAvailable( | |
| 837 env, observer_.obj(), ToJavaArrayOfStrings(env, urls).obj(), | |
| 838 ToJavaArrayOfStrings(env, favicon_urls).obj(), | |
| 839 ToJavaArrayOfStrings(env, large_icon_urls).obj()); | |
| 840 QueryMostVisitedURLs(); | |
| 841 } | |
| 842 | |
| 843 void MostVisitedSites::RecordImpressionUMAMetrics() { | |
| 844 for (size_t i = 0; i < current_suggestions_.size(); i++) { | |
| 845 std::string histogram = base::StringPrintf( | |
| 846 "NewTabPage.SuggestionsImpression.%s", | |
| 847 current_suggestions_[i]->GetSourceHistogramName().c_str()); | |
| 848 LogHistogramEvent(histogram, static_cast<int>(i), num_sites_); | |
| 849 } | |
| 850 } | |
| 851 | |
| 852 void MostVisitedSites::TopSitesLoaded(TopSites* top_sites) {} | |
| 853 | |
| 854 void MostVisitedSites::TopSitesChanged(TopSites* top_sites, | |
| 855 ChangeReason change_reason) { | |
| 856 if (mv_source_ == TOP_SITES) { | |
| 857 // The displayed suggestions are invalidated. | |
| 858 InitiateTopSitesQuery(); | |
| 859 } | |
| 860 } | |
| 861 | |
| 862 static jlong Init(JNIEnv* env, | |
| 863 const JavaParamRef<jobject>& obj, | |
| 864 const JavaParamRef<jobject>& jprofile) { | |
| 865 MostVisitedSites* most_visited_sites = | |
| 866 new MostVisitedSites(ProfileAndroid::FromProfileAndroid(jprofile)); | |
| 867 return reinterpret_cast<intptr_t>(most_visited_sites); | |
| 868 } | |
| OLD | NEW |