blob: f138669fac7138c79cfa913209ab54426bd34157 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/android/most_visited_sites.h"
#include <utility>
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/metrics/sparse_histogram.h"
#include "base/prefs/pref_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/android/popular_sites.h"
#include "chrome/browser/history/top_sites_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_android.h"
#include "chrome/browser/search/suggestions/suggestions_service_factory.h"
#include "chrome/browser/search/suggestions/suggestions_source.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/thumbnails/thumbnail_list_source.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/browser_sync/browser/profile_sync_service.h"
#include "components/history/core/browser/top_sites.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/suggestions/suggestions_service.h"
#include "components/suggestions/suggestions_utils.h"
#include "components/variations/variations_associated_data.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/url_data_source.h"
#include "jni/MostVisitedSites_jni.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/android/java_bitmap.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "url/gurl.h"
using base::android::AttachCurrentThread;
using base::android::ConvertJavaStringToUTF8;
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;
using base::android::ToJavaArrayOfStrings;
using content::BrowserThread;
using history::TopSites;
using suggestions::ChromeSuggestion;
using suggestions::SuggestionsProfile;
using suggestions::SuggestionsService;
using suggestions::SuggestionsServiceFactory;
using suggestions::SyncState;
namespace {
// Identifiers for the various tile sources.
const char kHistogramClientName[] = "client";
const char kHistogramServerName[] = "server";
const char kHistogramServerFormat[] = "server%d";
const char kHistogramPopularName[] = "popular";
const char kPopularSitesFieldTrialName[] = "NTPPopularSites";
// The visual type of a most visited tile.
//
// These values must stay in sync with the MostVisitedTileType enum
// in histograms.xml.
//
// A Java counterpart will be generated for this enum.
// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.ntp
enum MostVisitedTileType {
// The icon or thumbnail hasn't loaded yet.
NONE,
// The item displays a site's actual favicon or touch icon.
ICON_REAL,
// The item displays a color derived from the site's favicon or touch icon.
ICON_COLOR,
// The item displays a default gray box in place of an icon.
ICON_DEFAULT,
NUM_TILE_TYPES,
};
scoped_ptr<SkBitmap> MaybeFetchLocalThumbnail(
const GURL& url,
const scoped_refptr<TopSites>& top_sites) {
DCHECK_CURRENTLY_ON(BrowserThread::DB);
scoped_refptr<base::RefCountedMemory> image;
scoped_ptr<SkBitmap> bitmap;
if (top_sites && top_sites->GetPageThumbnail(url, false, &image))
bitmap.reset(gfx::JPEGCodec::Decode(image->front(), image->size()));
return bitmap;
}
// Log an event for a given |histogram| at a given element |position|. This
// routine exists because regular histogram macros are cached thus can't be used
// if the name of the histogram will change at a given call site.
void LogHistogramEvent(const std::string& histogram,
int position,
int num_sites) {
base::HistogramBase* counter = base::LinearHistogram::FactoryGet(
histogram,
1,
num_sites,
num_sites + 1,
base::Histogram::kUmaTargetedHistogramFlag);
if (counter)
counter->Add(position);
}
// Return the current SyncState for use with the SuggestionsService.
SyncState GetSyncState(Profile* profile) {
ProfileSyncService* sync =
ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
if (!sync)
return SyncState::SYNC_OR_HISTORY_SYNC_DISABLED;
return suggestions::GetSyncState(
sync->CanSyncStart(),
sync->IsSyncActive() && sync->ConfigurationDone(),
sync->GetActiveDataTypes().Has(syncer::HISTORY_DELETE_DIRECTIVES));
}
bool ShouldShowPopularSites() {
// Note: It's important to query the field trial state first, to ensure that
// UMA reports the correct group.
const std::string group_name =
base::FieldTrialList::FindFullName(kPopularSitesFieldTrialName);
base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
if (cmd_line->HasSwitch(switches::kDisableNTPPopularSites))
return false;
if (cmd_line->HasSwitch(switches::kEnableNTPPopularSites))
return true;
return base::StartsWith(group_name, "Enabled",
base::CompareCase::INSENSITIVE_ASCII);
}
std::string GetPopularSitesCountry() {
return variations::GetVariationParamValue(kPopularSitesFieldTrialName,
"country");
}
std::string GetPopularSitesVersion() {
return variations::GetVariationParamValue(kPopularSitesFieldTrialName,
"version");
}
// Determine whether we need any popular suggestions to fill up a grid of
// |num_tiles| tiles.
bool NeedPopularSites(const PrefService* prefs, size_t num_tiles) {
const base::ListValue* source_list =
prefs->GetList(prefs::kNTPSuggestionsIsPersonal);
// If there aren't enough previous suggestions to fill the grid, we need
// popular suggestions.
if (source_list->GetSize() < num_tiles)
return true;
// Otherwise, if any of the previous suggestions is not personal, then also
// get popular suggestions.
for (size_t i = 0; i < num_tiles; ++i) {
bool is_personal = false;
if (source_list->GetBoolean(i, &is_personal) && !is_personal)
return true;
}
// The whole grid is already filled with personal suggestions, no point in
// bothering with popular ones.
return false;
}
} // namespace
MostVisitedSites::Suggestion::Suggestion(const base::string16& title,
const std::string& url,
MostVisitedSource source)
: title(title), url(url), source(source), provider_index(-1) {}
MostVisitedSites::Suggestion::Suggestion(const base::string16& title,
const GURL& url,
MostVisitedSource source)
: title(title), url(url), source(source), provider_index(-1) {}
MostVisitedSites::Suggestion::Suggestion(const base::string16& title,
const std::string& url,
MostVisitedSource source,
int provider_index)
: title(title), url(url), source(source), provider_index(provider_index) {
DCHECK_EQ(MostVisitedSites::SUGGESTIONS_SERVICE, source);
}
MostVisitedSites::Suggestion::~Suggestion() {}
std::string MostVisitedSites::Suggestion::GetSourceHistogramName() const {
switch (source) {
case MostVisitedSites::TOP_SITES:
return kHistogramClientName;
case MostVisitedSites::POPULAR:
return kHistogramPopularName;
case MostVisitedSites::SUGGESTIONS_SERVICE:
return provider_index >= 0
? base::StringPrintf(kHistogramServerFormat, provider_index)
: kHistogramServerName;
}
NOTREACHED();
return std::string();
}
MostVisitedSites::MostVisitedSites(Profile* profile)
: profile_(profile), num_sites_(0), received_most_visited_sites_(false),
received_popular_sites_(false), recorded_uma_(false),
scoped_observer_(this), weak_ptr_factory_(this) {
// Register the debugging page for the Suggestions Service and the thumbnails
// debugging page.
content::URLDataSource::Add(profile_,
new suggestions::SuggestionsSource(profile_));
content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_));
// Register this class as an observer to the sync service. It is important to
// be notified of changes in the sync state such as initialization, sync
// being enabled or disabled, etc.
ProfileSyncService* profile_sync_service =
ProfileSyncServiceFactory::GetForProfile(profile_);
if (profile_sync_service)
profile_sync_service->AddObserver(this);
}
MostVisitedSites::~MostVisitedSites() {
ProfileSyncService* profile_sync_service =
ProfileSyncServiceFactory::GetForProfile(profile_);
if (profile_sync_service && profile_sync_service->HasObserver(this))
profile_sync_service->RemoveObserver(this);
}
void MostVisitedSites::Destroy(JNIEnv* env, const JavaParamRef<jobject>& obj) {
delete this;
}
void MostVisitedSites::SetMostVisitedURLsObserver(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jobject>& j_observer,
jint num_sites) {
observer_.Reset(env, j_observer);
num_sites_ = num_sites;
if (ShouldShowPopularSites() &&
NeedPopularSites(profile_->GetPrefs(), num_sites_)) {
popular_sites_.reset(new PopularSites(
profile_,
GetPopularSitesCountry(),
GetPopularSitesVersion(),
false,
base::Bind(&MostVisitedSites::OnPopularSitesAvailable,
base::Unretained(this))));
} else {
received_popular_sites_ = true;
}
QueryMostVisitedURLs();
scoped_refptr<history::TopSites> top_sites =
TopSitesFactory::GetForProfile(profile_);
if (top_sites) {
// TopSites updates itself after a delay. To ensure up-to-date results,
// force an update now.
top_sites->SyncWithHistory();
// Register as TopSitesObserver so that we can update ourselves when the
// TopSites changes.
scoped_observer_.Add(top_sites.get());
}
}
void MostVisitedSites::GetURLThumbnail(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jstring>& j_url,
const JavaParamRef<jobject>& j_callback_obj) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
scoped_ptr<ScopedJavaGlobalRef<jobject>> j_callback(
new ScopedJavaGlobalRef<jobject>());
j_callback->Reset(env, j_callback_obj);
GURL url(ConvertJavaStringToUTF8(env, j_url));
scoped_refptr<TopSites> top_sites(TopSitesFactory::GetForProfile(profile_));
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::DB, FROM_HERE,
base::Bind(&MaybeFetchLocalThumbnail, url, top_sites),
base::Bind(&MostVisitedSites::OnLocalThumbnailFetched,
weak_ptr_factory_.GetWeakPtr(), url,
base::Passed(&j_callback)));
}
void MostVisitedSites::OnLocalThumbnailFetched(
const GURL& url,
scoped_ptr<ScopedJavaGlobalRef<jobject>> j_callback,
scoped_ptr<SkBitmap> bitmap) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!bitmap.get()) {
// A thumbnail is not locally available for |url|. Make sure it is put in
// the list to be fetched at the next visit to this site.
scoped_refptr<TopSites> top_sites(TopSitesFactory::GetForProfile(profile_));
if (top_sites)
top_sites->AddForcedURL(url, base::Time::Now());
// Also fetch a remote thumbnail if possible. PopularSites or the
// SuggestionsService can supply a thumbnail download URL.
SuggestionsService* suggestions_service =
SuggestionsServiceFactory::GetForProfile(profile_);
if (suggestions_service) {
if (popular_sites_) {
const std::vector<PopularSites::Site>& sites = popular_sites_->sites();
auto it = std::find_if(sites.begin(), sites.end(),
[&url](const PopularSites::Site& site) {
return site.url == url;
});
if (it != sites.end() && it->thumbnail_url.is_valid()) {
return suggestions_service->GetPageThumbnailWithURL(
url, it->thumbnail_url,
base::Bind(&MostVisitedSites::OnObtainedThumbnail,
weak_ptr_factory_.GetWeakPtr(), false,
base::Passed(&j_callback)));
}
}
if (mv_source_ == SUGGESTIONS_SERVICE) {
return suggestions_service->GetPageThumbnail(
url, base::Bind(&MostVisitedSites::OnObtainedThumbnail,
weak_ptr_factory_.GetWeakPtr(), false,
base::Passed(&j_callback)));
}
}
}
OnObtainedThumbnail(true, std::move(j_callback), url, bitmap.get());
}
void MostVisitedSites::OnObtainedThumbnail(
bool is_local_thumbnail,
scoped_ptr<ScopedJavaGlobalRef<jobject>> j_callback,
const GURL& url,
const SkBitmap* bitmap) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jobject> j_bitmap;
if (bitmap)
j_bitmap = gfx::ConvertToJavaBitmap(bitmap);
Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
env, j_callback->obj(), j_bitmap.obj(), is_local_thumbnail);
}
void MostVisitedSites::AddOrRemoveBlacklistedUrl(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jstring>& j_url,
jboolean add_url) {
GURL url(ConvertJavaStringToUTF8(env, j_url));
// Always blacklist in the local TopSites.
scoped_refptr<TopSites> top_sites = TopSitesFactory::GetForProfile(profile_);
if (top_sites) {
if (add_url) {
top_sites->AddBlacklistedURL(url);
} else {
top_sites->RemoveBlacklistedURL(url);
}
}
// Only blacklist in the server-side suggestions service if it's active.
if (mv_source_ == SUGGESTIONS_SERVICE) {
SuggestionsService* suggestions_service =
SuggestionsServiceFactory::GetForProfile(profile_);
DCHECK(suggestions_service);
suggestions::SuggestionsService::ResponseCallback callback(
base::Bind(&MostVisitedSites::OnSuggestionsProfileAvailable,
weak_ptr_factory_.GetWeakPtr()));
if (add_url) {
suggestions_service->BlacklistURL(url, callback, base::Closure());
} else {
suggestions_service->UndoBlacklistURL(url, callback, base::Closure());
}
}
}
void MostVisitedSites::RecordTileTypeMetrics(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jintArray>& jtile_types) {
std::vector<int> tile_types;
base::android::JavaIntArrayToIntVector(env, jtile_types, &tile_types);
DCHECK_EQ(current_suggestions_.size(), tile_types.size());
int counts_per_type[NUM_TILE_TYPES] = {0};
for (size_t i = 0; i < tile_types.size(); ++i) {
int tile_type = tile_types[i];
++counts_per_type[tile_type];
std::string histogram = base::StringPrintf(
"NewTabPage.TileType.%s",
current_suggestions_[i]->GetSourceHistogramName().c_str());
LogHistogramEvent(histogram, tile_type, NUM_TILE_TYPES);
}
UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.IconsReal",
counts_per_type[ICON_REAL]);
UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.IconsColor",
counts_per_type[ICON_COLOR]);
UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.IconsGray",
counts_per_type[ICON_DEFAULT]);
}
void MostVisitedSites::RecordOpenedMostVisitedItem(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
jint index,
jint tile_type) {
DCHECK_GE(index, 0);
DCHECK_LT(index, static_cast<int>(current_suggestions_.size()));
std::string histogram = base::StringPrintf(
"NewTabPage.MostVisited.%s",
current_suggestions_[index]->GetSourceHistogramName().c_str());
LogHistogramEvent(histogram, index, num_sites_);
histogram = base::StringPrintf(
"NewTabPage.TileTypeClicked.%s",
current_suggestions_[index]->GetSourceHistogramName().c_str());
LogHistogramEvent(histogram, tile_type, NUM_TILE_TYPES);
}
void MostVisitedSites::OnStateChanged() {
// There have been changes to the sync state. This class cares about a few
// (just initialized, enabled/disabled or history sync state changed). Re-run
// the query code which will use the proper state.
QueryMostVisitedURLs();
}
// static
bool MostVisitedSites::Register(JNIEnv* env) {
return RegisterNativesImpl(env);
}
// static
void MostVisitedSites::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterListPref(prefs::kNTPSuggestionsURL);
registry->RegisterListPref(prefs::kNTPSuggestionsIsPersonal);
}
void MostVisitedSites::QueryMostVisitedURLs() {
SuggestionsService* suggestions_service =
SuggestionsServiceFactory::GetForProfile(profile_);
if (suggestions_service) {
// Suggestions service is enabled, initiate a query.
suggestions_service->FetchSuggestionsData(
GetSyncState(profile_),
base::Bind(&MostVisitedSites::OnSuggestionsProfileAvailable,
weak_ptr_factory_.GetWeakPtr()));
} else {
InitiateTopSitesQuery();
}
}
void MostVisitedSites::InitiateTopSitesQuery() {
scoped_refptr<TopSites> top_sites = TopSitesFactory::GetForProfile(profile_);
if (!top_sites)
return;
top_sites->GetMostVisitedURLs(
base::Bind(&MostVisitedSites::OnMostVisitedURLsAvailable,
weak_ptr_factory_.GetWeakPtr()),
false);
}
void MostVisitedSites::OnMostVisitedURLsAvailable(
const history::MostVisitedURLList& visited_list) {
ScopedVector<Suggestion> suggestions;
size_t num_tiles =
std::min(visited_list.size(), static_cast<size_t>(num_sites_));
for (size_t i = 0; i < num_tiles; ++i) {
const history::MostVisitedURL& visited = visited_list[i];
if (visited.url.is_empty()) {
num_tiles = i;
break; // This is the signal that there are no more real visited sites.
}
suggestions.push_back(
new Suggestion(visited.title, visited.url.spec(), TOP_SITES));
}
received_most_visited_sites_ = true;
mv_source_ = TOP_SITES;
AddPopularSites(&suggestions);
NotifyMostVisitedURLsObserver();
}
void MostVisitedSites::OnSuggestionsProfileAvailable(
const SuggestionsProfile& suggestions_profile) {
int num_tiles = suggestions_profile.suggestions_size();
// With no server suggestions, fall back to local Most Visited.
if (num_tiles == 0) {
InitiateTopSitesQuery();
return;
}
if (num_sites_ < num_tiles)
num_tiles = num_sites_;
ScopedVector<Suggestion> suggestions;
for (int i = 0; i < num_tiles; ++i) {
const ChromeSuggestion& suggestion = suggestions_profile.suggestions(i);
suggestions.push_back(new Suggestion(
base::UTF8ToUTF16(suggestion.title()), suggestion.url(),
SUGGESTIONS_SERVICE,
suggestion.providers_size() > 0 ? suggestion.providers(0) : -1));
}
received_most_visited_sites_ = true;
mv_source_ = SUGGESTIONS_SERVICE;
AddPopularSites(&suggestions);
NotifyMostVisitedURLsObserver();
}
void MostVisitedSites::AddPopularSites(
ScopedVector<Suggestion>* personal_suggestions) {
size_t num_personal_suggestions = personal_suggestions->size();
DCHECK_LE(num_personal_suggestions, static_cast<size_t>(num_sites_));
// Collect non-blacklisted popular suggestions, skipping those already present
// in the personal suggestions.
size_t num_popular_suggestions = num_sites_ - num_personal_suggestions;
ScopedVector<Suggestion> popular_suggestions;
popular_suggestions.reserve(num_popular_suggestions);
if (num_popular_suggestions > 0 && popular_sites_) {
std::set<std::string> personal_hosts;
for (const Suggestion* suggestion : *personal_suggestions)
personal_hosts.insert(suggestion->url.host());
scoped_refptr<TopSites> top_sites(TopSitesFactory::GetForProfile(profile_));
for (const PopularSites::Site& popular_site : popular_sites_->sites()) {
// Skip blacklisted sites.
if (top_sites && top_sites->IsBlacklisted(popular_site.url))
continue;
std::string host = popular_site.url.host();
// Skip suggestions already present in personal.
if (personal_hosts.find(host) != personal_hosts.end())
continue;
popular_suggestions.push_back(
new Suggestion(popular_site.title, popular_site.url, POPULAR));
if (popular_suggestions.size() >= num_popular_suggestions)
break;
}
}
num_popular_suggestions = popular_suggestions.size();
size_t num_actual_tiles = num_personal_suggestions + num_popular_suggestions;
std::vector<std::string> old_sites_url;
std::vector<bool> old_sites_is_personal;
GetPreviousNTPSites(num_actual_tiles, &old_sites_url, &old_sites_is_personal);
ScopedVector<Suggestion> merged_suggestions =
MergeSuggestions(personal_suggestions, &popular_suggestions,
old_sites_url, old_sites_is_personal);
DCHECK_EQ(num_actual_tiles, merged_suggestions.size());
current_suggestions_.swap(merged_suggestions);
if (received_popular_sites_)
SaveCurrentNTPSites();
}
// static
ScopedVector<MostVisitedSites::Suggestion> MostVisitedSites::MergeSuggestions(
ScopedVector<Suggestion>* personal_suggestions,
ScopedVector<Suggestion>* popular_suggestions,
const std::vector<std::string>& old_sites_url,
const std::vector<bool>& old_sites_is_personal) {
size_t num_personal_suggestions = personal_suggestions->size();
size_t num_popular_suggestions = popular_suggestions->size();
size_t num_tiles = num_popular_suggestions + num_personal_suggestions;
ScopedVector<Suggestion> merged_suggestions;
merged_suggestions.resize(num_tiles);
size_t num_old_tiles = old_sites_url.size();
DCHECK_LE(num_old_tiles, num_tiles);
DCHECK_EQ(num_old_tiles, old_sites_is_personal.size());
std::vector<std::string> old_sites_host;
old_sites_host.reserve(num_old_tiles);
// Only populate the hosts for popular suggestions as only they can be
// replaced by host. Personal suggestions require an exact url match to be
// replaced.
for (size_t i = 0; i < num_old_tiles; ++i) {
old_sites_host.push_back(old_sites_is_personal[i]
? std::string()
: GURL(old_sites_url[i]).host());
}
// Insert personal suggestions if they existed previously.
std::vector<size_t> new_personal_suggestions = InsertMatchingSuggestions(
personal_suggestions, &merged_suggestions, old_sites_url, old_sites_host);
// Insert popular suggestions if they existed previously.
std::vector<size_t> new_popular_suggestions = InsertMatchingSuggestions(
popular_suggestions, &merged_suggestions, old_sites_url, old_sites_host);
// Insert leftover personal suggestions.
size_t filled_so_far = InsertAllSuggestions(
0, new_personal_suggestions, personal_suggestions, &merged_suggestions);
// Insert leftover popular suggestions.
InsertAllSuggestions(filled_so_far, new_popular_suggestions,
popular_suggestions, &merged_suggestions);
return merged_suggestions;
}
void MostVisitedSites::GetPreviousNTPSites(
size_t num_tiles,
std::vector<std::string>* old_sites_url,
std::vector<bool>* old_sites_is_personal) const {
const PrefService* prefs = profile_->GetPrefs();
const base::ListValue* url_list = prefs->GetList(prefs::kNTPSuggestionsURL);
const base::ListValue* source_list =
prefs->GetList(prefs::kNTPSuggestionsIsPersonal);
DCHECK_EQ(url_list->GetSize(), source_list->GetSize());
if (url_list->GetSize() < num_tiles)
num_tiles = url_list->GetSize();
if (num_tiles == 0) {
// No fallback required as Personal suggestions take precedence anyway.
return;
}
old_sites_url->reserve(num_tiles);
old_sites_is_personal->reserve(num_tiles);
for (size_t i = 0; i < num_tiles; ++i) {
std::string url_string;
bool success = url_list->GetString(i, &url_string);
DCHECK(success);
old_sites_url->push_back(url_string);
bool is_personal;
success = source_list->GetBoolean(i, &is_personal);
DCHECK(success);
old_sites_is_personal->push_back(is_personal);
}
}
void MostVisitedSites::SaveCurrentNTPSites() {
base::ListValue url_list;
base::ListValue source_list;
for (const Suggestion* suggestion : current_suggestions_) {
url_list.AppendString(suggestion->url.spec());
source_list.AppendBoolean(suggestion->source != MostVisitedSites::POPULAR);
}
PrefService* prefs = profile_->GetPrefs();
prefs->Set(prefs::kNTPSuggestionsIsPersonal, source_list);
prefs->Set(prefs::kNTPSuggestionsURL, url_list);
}
// static
std::vector<size_t> MostVisitedSites::InsertMatchingSuggestions(
ScopedVector<Suggestion>* src_suggestions,
ScopedVector<Suggestion>* dst_suggestions,
const std::vector<std::string>& match_urls,
const std::vector<std::string>& match_hosts) {
std::vector<size_t> unmatched_suggestions;
size_t num_src_suggestions = src_suggestions->size();
size_t num_matchers = match_urls.size();
for (size_t i = 0; i < num_src_suggestions; ++i) {
size_t position;
for (position = 0; position < num_matchers; ++position) {
if ((*dst_suggestions)[position] != nullptr)
continue;
if (match_urls[position] == (*src_suggestions)[i]->url.spec())
break;
// match_hosts is only populated for suggestions which can be replaced by
// host matching like popular suggestions.
if (match_hosts[position] == (*src_suggestions)[i]->url.host())
break;
}
if (position == num_matchers) {
unmatched_suggestions.push_back(i);
} else {
// A move is required as the source and destination containers own the
// elements.
std::swap((*dst_suggestions)[position], (*src_suggestions)[i]);
}
}
return unmatched_suggestions;
}
// static
size_t MostVisitedSites::InsertAllSuggestions(
size_t start_position,
const std::vector<size_t>& insert_positions,
ScopedVector<Suggestion>* src_suggestions,
ScopedVector<Suggestion>* dst_suggestions) {
size_t num_inserts = insert_positions.size();
size_t num_dests = dst_suggestions->size();
size_t src_pos = 0;
size_t i = start_position;
for (; i < num_dests && src_pos < num_inserts; ++i) {
if ((*dst_suggestions)[i] != nullptr)
continue;
size_t src = insert_positions[src_pos++];
std::swap((*dst_suggestions)[i], (*src_suggestions)[src]);
}
// Return destination postions filled so far which becomes the start_position
// for future runs.
return i;
}
void MostVisitedSites::NotifyMostVisitedURLsObserver() {
size_t num_suggestions = current_suggestions_.size();
if (received_most_visited_sites_ && received_popular_sites_ &&
!recorded_uma_) {
RecordImpressionUMAMetrics();
UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.NumberOfTiles", num_suggestions);
recorded_uma_ = true;
}
std::vector<base::string16> titles;
std::vector<std::string> urls;
titles.reserve(num_suggestions);
urls.reserve(num_suggestions);
for (const Suggestion* suggestion : current_suggestions_) {
titles.push_back(suggestion->title);
urls.push_back(suggestion->url.spec());
}
JNIEnv* env = AttachCurrentThread();
DCHECK_EQ(titles.size(), urls.size());
Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
env, observer_.obj(), ToJavaArrayOfStrings(env, titles).obj(),
ToJavaArrayOfStrings(env, urls).obj());
}
void MostVisitedSites::OnPopularSitesAvailable(bool success) {
received_popular_sites_ = true;
if (!success) {
LOG(WARNING) << "Download of popular sites failed";
return;
}
if (observer_.is_null())
return;
std::vector<std::string> urls;
std::vector<std::string> favicon_urls;
std::vector<std::string> large_icon_urls;
for (const PopularSites::Site& popular_site : popular_sites_->sites()) {
urls.push_back(popular_site.url.spec());
favicon_urls.push_back(popular_site.favicon_url.spec());
large_icon_urls.push_back(popular_site.large_icon_url.spec());
}
JNIEnv* env = AttachCurrentThread();
Java_MostVisitedURLsObserver_onPopularURLsAvailable(
env, observer_.obj(), ToJavaArrayOfStrings(env, urls).obj(),
ToJavaArrayOfStrings(env, favicon_urls).obj(),
ToJavaArrayOfStrings(env, large_icon_urls).obj());
QueryMostVisitedURLs();
}
void MostVisitedSites::RecordImpressionUMAMetrics() {
for (size_t i = 0; i < current_suggestions_.size(); i++) {
std::string histogram = base::StringPrintf(
"NewTabPage.SuggestionsImpression.%s",
current_suggestions_[i]->GetSourceHistogramName().c_str());
LogHistogramEvent(histogram, static_cast<int>(i), num_sites_);
}
}
void MostVisitedSites::TopSitesLoaded(history::TopSites* top_sites) {
}
void MostVisitedSites::TopSitesChanged(history::TopSites* top_sites,
ChangeReason change_reason) {
if (mv_source_ == TOP_SITES) {
// The displayed suggestions are invalidated.
InitiateTopSitesQuery();
}
}
static jlong Init(JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jobject>& jprofile) {
MostVisitedSites* most_visited_sites =
new MostVisitedSites(ProfileAndroid::FromProfileAndroid(jprofile));
return reinterpret_cast<intptr_t>(most_visited_sites);
}