#include "chrome/browser/android/most_visited_sites.h"
+#include <string>
+#include <vector>
+
#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/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
#include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/history/history_types.h"
#include "chrome/browser/history/top_sites.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/thumbnails/thumbnail_list_source.h"
+#include "components/suggestions/suggestions_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_source.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"
using base::android::CheckException;
using content::BrowserThread;
using history::TopSites;
+using suggestions::ChromeSuggestion;
+using suggestions::SuggestionsProfile;
+using suggestions::SuggestionsService;
+using suggestions::SuggestionsServiceFactory;
namespace {
+// Total number of tiles displayed.
+const char kNumTilesHistogramName[] = "NewTabPage.NumberOfTiles";
+// Tracking thumbnails.
+const char kNumLocalThumbnailTilesHistogramName[] =
+ "NewTabPage.NumberOfThumbnailTiles";
+const char kNumEmptyTilesHistogramName[] = "NewTabPage.NumberOfGrayTiles";
+const char kNumServerTilesHistogramName[] = "NewTabPage.NumberOfExternalTiles";
+// Client suggestion opened.
+const char kOpenedItemClientHistogramName[] = "NewTabPage.MostVisited.client";
+// Control group suggestion opened.
+const char kOpenedItemControlHistogramName[] = "NewTabPage.MostVisited.client0";
+// Server suggestion opened, no provider.
+const char kOpenedItemServerHistogramName[] = "NewTabPage.MostVisited.server";
+// Server suggestion opened with provider.
+const char kOpenedItemServerProviderHistogramFormat[] =
+ "NewTabPage.MostVisited.server%d";
+// Client impression.
+const char kImpressionClientHistogramName[] =
+ "NewTabPage.SuggestionsImpression.client";
+// Control group impression.
+const char kImpressionControlHistogramName[] =
+ "NewTabPage.SuggestionsImpression.client0";
+// Server suggestion impression, no provider.
+const char kImpressionServerHistogramName[] =
+ "NewTabPage.SuggestionsImpression.server";
+// Server suggestion impression with provider.
+const char kImpressionServerHistogramFormat[] =
+ "NewTabPage.SuggestionsImpression.server%d";
+
void ExtractMostVisitedTitlesAndURLs(
const history::MostVisitedURLList& visited_list,
std::vector<base::string16>* titles,
}
}
-void OnMostVisitedURLsAvailable(
- ScopedJavaGlobalRef<jobject>* j_observer,
- int num_sites,
- const history::MostVisitedURLList& visited_list) {
- std::vector<base::string16> titles;
- std::vector<std::string> urls;
- ExtractMostVisitedTitlesAndURLs(visited_list, &titles, &urls, num_sites);
-
- JNIEnv* env = AttachCurrentThread();
- Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
- env,
- j_observer->obj(),
- ToJavaArrayOfStrings(env, titles).obj(),
- ToJavaArrayOfStrings(env, urls).obj());
-}
-
SkBitmap ExtractThumbnail(const base::RefCountedMemory& image_data) {
scoped_ptr<SkBitmap> image(gfx::JPEGCodec::Decode(
image_data.front(),
return image.get() ? *image : SkBitmap();
}
-void OnObtainedThumbnail(
- ScopedJavaGlobalRef<jobject>* bitmap,
- ScopedJavaGlobalRef<jobject>* j_callback) {
+void AddForcedURLOnUIThread(scoped_refptr<history::TopSites> top_sites,
+ const GURL& url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- JNIEnv* env = AttachCurrentThread();
- Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
- env, j_callback->obj(), bitmap->obj());
+ top_sites->AddForcedURL(url, base::Time::Now());
}
+// Runs on the DB thread.
void GetUrlThumbnailTask(
std::string url_string,
scoped_refptr<TopSites> top_sites,
- ScopedJavaGlobalRef<jobject>* j_callback) {
+ ScopedJavaGlobalRef<jobject>* j_callback,
+ MostVisitedSites::LookupSuccessCallback lookup_success_ui_callback,
+ base::Closure lookup_failed_ui_callback) {
JNIEnv* env = AttachCurrentThread();
ScopedJavaGlobalRef<jobject>* j_bitmap_ref =
env,
gfx::ConvertToJavaBitmap(&thumbnail_bitmap).obj());
}
+ } else {
+ // A thumbnail is not locally available for |gurl|. Make sure it is put in
+ // the list to be fetched at the next visit to this site.
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(AddForcedURLOnUIThread, top_sites, gurl));
+
+ // If appropriate, return on the UI thread to execute the proper callback.
+ if (!lookup_failed_ui_callback.is_null()) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE, lookup_failed_ui_callback);
+ delete j_bitmap_ref;
+ return;
+ }
}
// Since j_callback is owned by this callback, when the callback falls out of
new ScopedJavaGlobalRef<jobject>(*j_callback);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
- base::Bind(
- &OnObtainedThumbnail,
- base::Owned(j_bitmap_ref), base::Owned(j_callback_pass)));
+ base::Bind(lookup_success_ui_callback, base::Owned(j_bitmap_ref),
+ base::Owned(j_callback_pass)));
+}
+
+// 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);
}
} // namespace
MostVisitedSites::MostVisitedSites(Profile* profile)
- : profile_(profile), num_sites_(0) {
+ : profile_(profile), num_sites_(0), is_control_group_(false),
+ num_local_thumbs_(0), num_server_thumbs_(0), num_empty_thumbs_(0),
+ 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_));
}
MostVisitedSites::~MostVisitedSites() {
delete this;
}
+void MostVisitedSites::OnLoadingComplete(JNIEnv* env, jobject obj) {
+ RecordUMAMetrics();
+}
+
void MostVisitedSites::SetMostVisitedURLsObserver(JNIEnv* env,
jobject obj,
jobject j_observer,
}
}
-// May be called from any thread
+// Called from the UI Thread.
void MostVisitedSites::GetURLThumbnail(JNIEnv* env,
jobject obj,
jstring url,
jobject j_callback_obj) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ScopedJavaGlobalRef<jobject>* j_callback =
new ScopedJavaGlobalRef<jobject>();
j_callback->Reset(env, j_callback_obj);
std::string url_string = ConvertJavaStringToUTF8(env, url);
scoped_refptr<TopSites> top_sites(profile_->GetTopSites());
+
+ // If the Suggestions service is enabled, create a callback to fetch a
+ // server thumbnail from it, in case the local thumbnail is not found.
+ SuggestionsService* suggestions_service =
+ SuggestionsServiceFactory::GetForProfile(profile_);
+ base::Closure lookup_failed_callback = suggestions_service ?
+ base::Bind(&MostVisitedSites::GetSuggestionsThumbnailOnUIThread,
+ weak_ptr_factory_.GetWeakPtr(),
+ suggestions_service, url_string,
+ base::Owned(new ScopedJavaGlobalRef<jobject>(*j_callback))) :
+ base::Closure();
+ LookupSuccessCallback lookup_success_callback =
+ base::Bind(&MostVisitedSites::OnObtainedThumbnail,
+ weak_ptr_factory_.GetWeakPtr());
+
BrowserThread::PostTask(
- BrowserThread::DB, FROM_HERE, base::Bind(
- &GetUrlThumbnailTask,
- url_string,
- top_sites, base::Owned(j_callback)));
+ BrowserThread::DB, FROM_HERE,
+ base::Bind(
+ &GetUrlThumbnailTask, url_string, top_sites,
+ base::Owned(j_callback), lookup_success_callback,
+ lookup_failed_callback));
}
void MostVisitedSites::BlacklistUrl(JNIEnv* env,
jobject obj,
jstring j_url) {
- TopSites* top_sites = profile_->GetTopSites();
- if (!top_sites)
- return;
+ std::string url = ConvertJavaStringToUTF8(env, j_url);
+
+ switch (mv_source_) {
+ case TOP_SITES: {
+ TopSites* top_sites = profile_->GetTopSites();
+ DCHECK(top_sites);
+ top_sites->AddBlacklistedURL(GURL(url));
+ break;
+ }
- std::string url_string = ConvertJavaStringToUTF8(env, j_url);
- top_sites->AddBlacklistedURL(GURL(url_string));
+ case SUGGESTIONS_SERVICE: {
+ SuggestionsService* suggestions_service =
+ SuggestionsServiceFactory::GetForProfile(profile_);
+ DCHECK(suggestions_service);
+ suggestions_service->BlacklistURL(
+ GURL(url),
+ base::Bind(
+ &MostVisitedSites::OnSuggestionsProfileAvailable,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Owned(new ScopedJavaGlobalRef<jobject>(observer_))));
+ break;
+ }
+ }
+}
+
+void MostVisitedSites::RecordOpenedMostVisitedItem(JNIEnv* env,
+ jobject obj,
+ jint index) {
+ switch (mv_source_) {
+ case TOP_SITES: {
+ const std::string histogram = is_control_group_ ?
+ kOpenedItemControlHistogramName : kOpenedItemClientHistogramName;
+ LogHistogramEvent(histogram, index, num_sites_);
+ break;
+ }
+ case SUGGESTIONS_SERVICE: {
+ if (server_suggestions_.suggestions_size() > index) {
+ if (server_suggestions_.suggestions(index).providers_size()) {
+ std::string histogram = base::StringPrintf(
+ kOpenedItemServerProviderHistogramFormat,
+ server_suggestions_.suggestions(index).providers(0));
+ LogHistogramEvent(histogram, index, num_sites_);
+ } else {
+ UMA_HISTOGRAM_SPARSE_SLOWLY(kOpenedItemServerHistogramName, index);
+ }
+ }
+ break;
+ }
+ }
}
void MostVisitedSites::Observe(int type,
const content::NotificationDetails& details) {
DCHECK_EQ(type, chrome::NOTIFICATION_TOP_SITES_CHANGED);
- // Most visited urls changed, query again.
- QueryMostVisitedURLs();
+ if (mv_source_ == TOP_SITES) {
+ // The displayed suggestions are invalidated.
+ QueryMostVisitedURLs();
+ }
}
// static
}
void MostVisitedSites::QueryMostVisitedURLs() {
+ SuggestionsService* suggestions_service =
+ SuggestionsServiceFactory::GetForProfile(profile_);
+ if (suggestions_service) {
+ // Suggestions service is enabled, initiate a query.
+ suggestions_service->FetchSuggestionsData(
+ base::Bind(
+ &MostVisitedSites::OnSuggestionsProfileAvailable,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Owned(new ScopedJavaGlobalRef<jobject>(observer_))));
+ } else {
+ InitiateTopSitesQuery();
+ }
+}
+
+void MostVisitedSites::InitiateTopSitesQuery() {
TopSites* top_sites = profile_->GetTopSites();
if (!top_sites)
return;
top_sites->GetMostVisitedURLs(
base::Bind(
- &OnMostVisitedURLsAvailable,
+ &MostVisitedSites::OnMostVisitedURLsAvailable,
+ weak_ptr_factory_.GetWeakPtr(),
base::Owned(new ScopedJavaGlobalRef<jobject>(observer_)),
num_sites_),
false);
}
+void MostVisitedSites::OnMostVisitedURLsAvailable(
+ ScopedJavaGlobalRef<jobject>* j_observer,
+ int num_sites,
+ const history::MostVisitedURLList& visited_list) {
+ std::vector<base::string16> titles;
+ std::vector<std::string> urls;
+ ExtractMostVisitedTitlesAndURLs(visited_list, &titles, &urls, num_sites);
+
+ mv_source_ = TOP_SITES;
+
+ int num_tiles = urls.size();
+ UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName, num_tiles);
+ const std::string histogram = is_control_group_ ?
+ kImpressionControlHistogramName : kImpressionClientHistogramName;
+ for (int i = 0; i < num_tiles; ++i) {
+ LogHistogramEvent(histogram, i, num_sites_);
+ }
+
+ JNIEnv* env = AttachCurrentThread();
+ Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
+ env,
+ j_observer->obj(),
+ ToJavaArrayOfStrings(env, titles).obj(),
+ ToJavaArrayOfStrings(env, urls).obj());
+}
+
+void MostVisitedSites::OnSuggestionsProfileAvailable(
+ ScopedJavaGlobalRef<jobject>* j_observer,
+ const SuggestionsProfile& suggestions_profile) {
+ int size = suggestions_profile.suggestions_size();
+
+ // Determine if the user is in a control group (they would have received
+ // suggestions, but are in a group where they shouldn't).
+ is_control_group_ = size && SuggestionsService::IsControlGroup();
+
+ // If no suggestions data is available or the user is in a control group,
+ // initiate Top Sites query.
+ if (is_control_group_ || !size) {
+ InitiateTopSitesQuery();
+ return;
+ }
+
+ std::vector<base::string16> titles;
+ std::vector<std::string> urls;
+
+ int i = 0;
+ for (; i < size && i < num_sites_; ++i) {
+ const ChromeSuggestion& suggestion = suggestions_profile.suggestions(i);
+ titles.push_back(base::UTF8ToUTF16(suggestion.title()));
+ urls.push_back(suggestion.url());
+ if (suggestion.providers_size()) {
+ std::string histogram = base::StringPrintf(
+ kImpressionServerHistogramFormat, suggestion.providers(0));
+ LogHistogramEvent(histogram, i, num_sites_);
+ } else {
+ UMA_HISTOGRAM_SPARSE_SLOWLY(kImpressionServerHistogramName, i);
+ }
+ }
+ UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName, i);
+
+ mv_source_ = SUGGESTIONS_SERVICE;
+ // Keep a copy of the suggestions for eventual logging.
+ server_suggestions_ = suggestions_profile;
+
+ JNIEnv* env = AttachCurrentThread();
+ Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
+ env,
+ j_observer->obj(),
+ ToJavaArrayOfStrings(env, titles).obj(),
+ ToJavaArrayOfStrings(env, urls).obj());
+}
+
+void MostVisitedSites::OnObtainedThumbnail(
+ ScopedJavaGlobalRef<jobject>* bitmap,
+ ScopedJavaGlobalRef<jobject>* j_callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ JNIEnv* env = AttachCurrentThread();
+ if (bitmap->obj()) {
+ num_local_thumbs_++;
+ } else {
+ num_empty_thumbs_++;
+ }
+ Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
+ env, j_callback->obj(), bitmap->obj());
+}
+
+void MostVisitedSites::GetSuggestionsThumbnailOnUIThread(
+ SuggestionsService* suggestions_service,
+ const std::string& url_string,
+ ScopedJavaGlobalRef<jobject>* j_callback) {
+ suggestions_service->GetPageThumbnail(
+ GURL(url_string),
+ base::Bind(&MostVisitedSites::OnSuggestionsThumbnailAvailable,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Owned(new ScopedJavaGlobalRef<jobject>(*j_callback))));
+}
+
+void MostVisitedSites::OnSuggestionsThumbnailAvailable(
+ ScopedJavaGlobalRef<jobject>* j_callback,
+ const GURL& url,
+ const SkBitmap* bitmap) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ JNIEnv* env = AttachCurrentThread();
+
+ ScopedJavaGlobalRef<jobject>* j_bitmap_ref =
+ new ScopedJavaGlobalRef<jobject>();
+ if (bitmap) {
+ num_server_thumbs_++;
+ j_bitmap_ref->Reset(
+ env,
+ gfx::ConvertToJavaBitmap(bitmap).obj());
+ } else {
+ num_empty_thumbs_++;
+ }
+
+ Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
+ env, j_callback->obj(), j_bitmap_ref->obj());
+}
+
+void MostVisitedSites::RecordUMAMetrics() {
+ UMA_HISTOGRAM_SPARSE_SLOWLY(kNumLocalThumbnailTilesHistogramName,
+ num_local_thumbs_);
+ num_local_thumbs_ = 0;
+ UMA_HISTOGRAM_SPARSE_SLOWLY(kNumEmptyTilesHistogramName, num_empty_thumbs_);
+ num_empty_thumbs_ = 0;
+ UMA_HISTOGRAM_SPARSE_SLOWLY(kNumServerTilesHistogramName, num_server_thumbs_);
+ num_server_thumbs_ = 0;
+}
+
static jlong Init(JNIEnv* env, jobject obj, jobject jprofile) {
MostVisitedSites* most_visited_sites =
new MostVisitedSites(ProfileAndroid::FromProfileAndroid(jprofile));