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.
5 #include "chrome/browser/android/most_visited_sites.h"
10 #include "base/android/jni_android.h"
11 #include "base/android/jni_array.h"
12 #include "base/android/jni_string.h"
13 #include "base/android/scoped_java_ref.h"
14 #include "base/callback.h"
15 #include "base/metrics/histogram.h"
16 #include "base/metrics/sparse_histogram.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/time/time.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/history/top_sites.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/profiles/profile_android.h"
25 #include "chrome/browser/search/suggestions/suggestions_service_factory.h"
26 #include "chrome/browser/search/suggestions/suggestions_source.h"
27 #include "chrome/browser/thumbnails/thumbnail_list_source.h"
28 #include "components/suggestions/suggestions_service.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "content/public/browser/notification_source.h"
31 #include "content/public/browser/url_data_source.h"
32 #include "jni/MostVisitedSites_jni.h"
33 #include "third_party/skia/include/core/SkBitmap.h"
34 #include "ui/gfx/android/java_bitmap.h"
35 #include "ui/gfx/codec/jpeg_codec.h"
37 using base::android::AttachCurrentThread;
38 using base::android::ConvertUTF8ToJavaString;
39 using base::android::ConvertJavaStringToUTF8;
40 using base::android::ScopedJavaGlobalRef;
41 using base::android::ToJavaArrayOfStrings;
42 using base::android::CheckException;
43 using content::BrowserThread;
44 using history::TopSites;
45 using suggestions::ChromeSuggestion;
46 using suggestions::SuggestionsProfile;
47 using suggestions::SuggestionsService;
48 using suggestions::SuggestionsServiceFactory;
52 // Total number of tiles displayed.
53 const char kNumTilesHistogramName[] = "NewTabPage.NumberOfTiles";
54 // Tracking thumbnails.
55 const char kNumLocalThumbnailTilesHistogramName[] =
56 "NewTabPage.NumberOfThumbnailTiles";
57 const char kNumEmptyTilesHistogramName[] = "NewTabPage.NumberOfGrayTiles";
58 const char kNumServerTilesHistogramName[] = "NewTabPage.NumberOfExternalTiles";
59 // Client suggestion opened.
60 const char kOpenedItemClientHistogramName[] = "NewTabPage.MostVisited.client";
61 // Control group suggestion opened.
62 const char kOpenedItemControlHistogramName[] = "NewTabPage.MostVisited.client0";
63 // Server suggestion opened, no provider.
64 const char kOpenedItemServerHistogramName[] = "NewTabPage.MostVisited.server";
65 // Server suggestion opened with provider.
66 const char kOpenedItemServerProviderHistogramFormat[] =
67 "NewTabPage.MostVisited.server%d";
69 const char kImpressionClientHistogramName[] =
70 "NewTabPage.SuggestionsImpression.client";
71 // Control group impression.
72 const char kImpressionControlHistogramName[] =
73 "NewTabPage.SuggestionsImpression.client0";
74 // Server suggestion impression, no provider.
75 const char kImpressionServerHistogramName[] =
76 "NewTabPage.SuggestionsImpression.server";
77 // Server suggestion impression with provider.
78 const char kImpressionServerHistogramFormat[] =
79 "NewTabPage.SuggestionsImpression.server%d";
81 void ExtractMostVisitedTitlesAndURLs(
82 const history::MostVisitedURLList& visited_list,
83 std::vector<base::string16>* titles,
84 std::vector<std::string>* urls,
86 size_t max = static_cast<size_t>(num_sites);
87 for (size_t i = 0; i < visited_list.size() && i < max; ++i) {
88 const history::MostVisitedURL& visited = visited_list[i];
90 if (visited.url.is_empty())
91 break; // This is the signal that there are no more real visited sites.
93 titles->push_back(visited.title);
94 urls->push_back(visited.url.spec());
98 SkBitmap ExtractThumbnail(const base::RefCountedMemory& image_data) {
99 scoped_ptr<SkBitmap> image(gfx::JPEGCodec::Decode(
102 return image.get() ? *image : SkBitmap();
105 void AddForcedURLOnUIThread(scoped_refptr<history::TopSites> top_sites,
107 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
108 top_sites->AddForcedURL(url, base::Time::Now());
111 // Runs on the DB thread.
112 void GetUrlThumbnailTask(
113 std::string url_string,
114 scoped_refptr<TopSites> top_sites,
115 ScopedJavaGlobalRef<jobject>* j_callback,
116 MostVisitedSites::LookupSuccessCallback lookup_success_ui_callback,
117 base::Closure lookup_failed_ui_callback) {
118 JNIEnv* env = AttachCurrentThread();
120 ScopedJavaGlobalRef<jobject>* j_bitmap_ref =
121 new ScopedJavaGlobalRef<jobject>();
123 GURL gurl(url_string);
125 scoped_refptr<base::RefCountedMemory> data;
126 if (top_sites->GetPageThumbnail(gurl, false, &data)) {
127 SkBitmap thumbnail_bitmap = ExtractThumbnail(*data.get());
128 if (!thumbnail_bitmap.empty()) {
131 gfx::ConvertToJavaBitmap(&thumbnail_bitmap).obj());
134 // A thumbnail is not locally available for |gurl|. Make sure it is put in
135 // the list to be fetched at the next visit to this site.
136 BrowserThread::PostTask(
137 BrowserThread::UI, FROM_HERE,
138 base::Bind(AddForcedURLOnUIThread, top_sites, gurl));
140 // If appropriate, return on the UI thread to execute the proper callback.
141 if (!lookup_failed_ui_callback.is_null()) {
142 BrowserThread::PostTask(
143 BrowserThread::UI, FROM_HERE, lookup_failed_ui_callback);
149 // Since j_callback is owned by this callback, when the callback falls out of
150 // scope it will be deleted. We need to pass ownership to the next callback.
151 ScopedJavaGlobalRef<jobject>* j_callback_pass =
152 new ScopedJavaGlobalRef<jobject>(*j_callback);
153 BrowserThread::PostTask(
154 BrowserThread::UI, FROM_HERE,
155 base::Bind(lookup_success_ui_callback, base::Owned(j_bitmap_ref),
156 base::Owned(j_callback_pass)));
159 // Log an event for a given |histogram| at a given element |position|. This
160 // routine exists because regular histogram macros are cached thus can't be used
161 // if the name of the histogram will change at a given call site.
162 void LogHistogramEvent(const std::string& histogram, int position,
164 base::HistogramBase* counter = base::LinearHistogram::FactoryGet(
169 base::Histogram::kUmaTargetedHistogramFlag);
171 counter->Add(position);
176 MostVisitedSites::MostVisitedSites(Profile* profile)
177 : profile_(profile), num_sites_(0), is_control_group_(false),
178 num_local_thumbs_(0), num_server_thumbs_(0), num_empty_thumbs_(0),
179 weak_ptr_factory_(this) {
180 // Register the debugging page for the Suggestions Service and the thumbnails
182 content::URLDataSource::Add(profile_,
183 new suggestions::SuggestionsSource(profile_));
184 content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_));
187 MostVisitedSites::~MostVisitedSites() {
190 void MostVisitedSites::Destroy(JNIEnv* env, jobject obj) {
194 void MostVisitedSites::OnLoadingComplete(JNIEnv* env, jobject obj) {
198 void MostVisitedSites::SetMostVisitedURLsObserver(JNIEnv* env,
202 observer_.Reset(env, j_observer);
203 num_sites_ = num_sites;
205 QueryMostVisitedURLs();
207 history::TopSites* top_sites = profile_->GetTopSites();
209 // TopSites updates itself after a delay. To ensure up-to-date results,
210 // force an update now.
211 top_sites->SyncWithHistory();
213 // Register for notification when TopSites changes so that we can update
215 registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED,
216 content::Source<history::TopSites>(top_sites));
220 // Called from the UI Thread.
221 void MostVisitedSites::GetURLThumbnail(JNIEnv* env,
224 jobject j_callback_obj) {
225 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
226 ScopedJavaGlobalRef<jobject>* j_callback =
227 new ScopedJavaGlobalRef<jobject>();
228 j_callback->Reset(env, j_callback_obj);
230 std::string url_string = ConvertJavaStringToUTF8(env, url);
231 scoped_refptr<TopSites> top_sites(profile_->GetTopSites());
233 // If the Suggestions service is enabled, create a callback to fetch a
234 // server thumbnail from it, in case the local thumbnail is not found.
235 SuggestionsService* suggestions_service =
236 SuggestionsServiceFactory::GetForProfile(profile_);
237 base::Closure lookup_failed_callback = suggestions_service ?
238 base::Bind(&MostVisitedSites::GetSuggestionsThumbnailOnUIThread,
239 weak_ptr_factory_.GetWeakPtr(),
240 suggestions_service, url_string,
241 base::Owned(new ScopedJavaGlobalRef<jobject>(*j_callback))) :
243 LookupSuccessCallback lookup_success_callback =
244 base::Bind(&MostVisitedSites::OnObtainedThumbnail,
245 weak_ptr_factory_.GetWeakPtr());
247 BrowserThread::PostTask(
248 BrowserThread::DB, FROM_HERE,
250 &GetUrlThumbnailTask, url_string, top_sites,
251 base::Owned(j_callback), lookup_success_callback,
252 lookup_failed_callback));
255 void MostVisitedSites::BlacklistUrl(JNIEnv* env,
258 std::string url = ConvertJavaStringToUTF8(env, j_url);
260 switch (mv_source_) {
262 TopSites* top_sites = profile_->GetTopSites();
264 top_sites->AddBlacklistedURL(GURL(url));
268 case SUGGESTIONS_SERVICE: {
269 SuggestionsService* suggestions_service =
270 SuggestionsServiceFactory::GetForProfile(profile_);
271 DCHECK(suggestions_service);
272 suggestions_service->BlacklistURL(
275 &MostVisitedSites::OnSuggestionsProfileAvailable,
276 weak_ptr_factory_.GetWeakPtr(),
277 base::Owned(new ScopedJavaGlobalRef<jobject>(observer_))));
283 void MostVisitedSites::RecordOpenedMostVisitedItem(JNIEnv* env,
286 switch (mv_source_) {
288 const std::string histogram = is_control_group_ ?
289 kOpenedItemControlHistogramName : kOpenedItemClientHistogramName;
290 LogHistogramEvent(histogram, index, num_sites_);
293 case SUGGESTIONS_SERVICE: {
294 if (server_suggestions_.suggestions_size() > index) {
295 if (server_suggestions_.suggestions(index).providers_size()) {
296 std::string histogram = base::StringPrintf(
297 kOpenedItemServerProviderHistogramFormat,
298 server_suggestions_.suggestions(index).providers(0));
299 LogHistogramEvent(histogram, index, num_sites_);
301 UMA_HISTOGRAM_SPARSE_SLOWLY(kOpenedItemServerHistogramName, index);
309 void MostVisitedSites::Observe(int type,
310 const content::NotificationSource& source,
311 const content::NotificationDetails& details) {
312 DCHECK_EQ(type, chrome::NOTIFICATION_TOP_SITES_CHANGED);
314 if (mv_source_ == TOP_SITES) {
315 // The displayed suggestions are invalidated.
316 QueryMostVisitedURLs();
321 bool MostVisitedSites::Register(JNIEnv* env) {
322 return RegisterNativesImpl(env);
325 void MostVisitedSites::QueryMostVisitedURLs() {
326 SuggestionsService* suggestions_service =
327 SuggestionsServiceFactory::GetForProfile(profile_);
328 if (suggestions_service) {
329 // Suggestions service is enabled, initiate a query.
330 suggestions_service->FetchSuggestionsData(
332 &MostVisitedSites::OnSuggestionsProfileAvailable,
333 weak_ptr_factory_.GetWeakPtr(),
334 base::Owned(new ScopedJavaGlobalRef<jobject>(observer_))));
336 InitiateTopSitesQuery();
340 void MostVisitedSites::InitiateTopSitesQuery() {
341 TopSites* top_sites = profile_->GetTopSites();
345 top_sites->GetMostVisitedURLs(
347 &MostVisitedSites::OnMostVisitedURLsAvailable,
348 weak_ptr_factory_.GetWeakPtr(),
349 base::Owned(new ScopedJavaGlobalRef<jobject>(observer_)),
354 void MostVisitedSites::OnMostVisitedURLsAvailable(
355 ScopedJavaGlobalRef<jobject>* j_observer,
357 const history::MostVisitedURLList& visited_list) {
358 std::vector<base::string16> titles;
359 std::vector<std::string> urls;
360 ExtractMostVisitedTitlesAndURLs(visited_list, &titles, &urls, num_sites);
362 mv_source_ = TOP_SITES;
364 int num_tiles = urls.size();
365 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName, num_tiles);
366 const std::string histogram = is_control_group_ ?
367 kImpressionControlHistogramName : kImpressionClientHistogramName;
368 for (int i = 0; i < num_tiles; ++i) {
369 LogHistogramEvent(histogram, i, num_sites_);
372 JNIEnv* env = AttachCurrentThread();
373 Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
376 ToJavaArrayOfStrings(env, titles).obj(),
377 ToJavaArrayOfStrings(env, urls).obj());
380 void MostVisitedSites::OnSuggestionsProfileAvailable(
381 ScopedJavaGlobalRef<jobject>* j_observer,
382 const SuggestionsProfile& suggestions_profile) {
383 int size = suggestions_profile.suggestions_size();
385 // Determine if the user is in a control group (they would have received
386 // suggestions, but are in a group where they shouldn't).
387 is_control_group_ = size && SuggestionsService::IsControlGroup();
389 // If no suggestions data is available or the user is in a control group,
390 // initiate Top Sites query.
391 if (is_control_group_ || !size) {
392 InitiateTopSitesQuery();
396 std::vector<base::string16> titles;
397 std::vector<std::string> urls;
400 for (; i < size && i < num_sites_; ++i) {
401 const ChromeSuggestion& suggestion = suggestions_profile.suggestions(i);
402 titles.push_back(base::UTF8ToUTF16(suggestion.title()));
403 urls.push_back(suggestion.url());
404 if (suggestion.providers_size()) {
405 std::string histogram = base::StringPrintf(
406 kImpressionServerHistogramFormat, suggestion.providers(0));
407 LogHistogramEvent(histogram, i, num_sites_);
409 UMA_HISTOGRAM_SPARSE_SLOWLY(kImpressionServerHistogramName, i);
412 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName, i);
414 mv_source_ = SUGGESTIONS_SERVICE;
415 // Keep a copy of the suggestions for eventual logging.
416 server_suggestions_ = suggestions_profile;
418 JNIEnv* env = AttachCurrentThread();
419 Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
422 ToJavaArrayOfStrings(env, titles).obj(),
423 ToJavaArrayOfStrings(env, urls).obj());
426 void MostVisitedSites::OnObtainedThumbnail(
427 ScopedJavaGlobalRef<jobject>* bitmap,
428 ScopedJavaGlobalRef<jobject>* j_callback) {
429 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
430 JNIEnv* env = AttachCurrentThread();
436 Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
437 env, j_callback->obj(), bitmap->obj());
440 void MostVisitedSites::GetSuggestionsThumbnailOnUIThread(
441 SuggestionsService* suggestions_service,
442 const std::string& url_string,
443 ScopedJavaGlobalRef<jobject>* j_callback) {
444 suggestions_service->GetPageThumbnail(
446 base::Bind(&MostVisitedSites::OnSuggestionsThumbnailAvailable,
447 weak_ptr_factory_.GetWeakPtr(),
448 base::Owned(new ScopedJavaGlobalRef<jobject>(*j_callback))));
451 void MostVisitedSites::OnSuggestionsThumbnailAvailable(
452 ScopedJavaGlobalRef<jobject>* j_callback,
454 const SkBitmap* bitmap) {
455 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
456 JNIEnv* env = AttachCurrentThread();
458 ScopedJavaGlobalRef<jobject>* j_bitmap_ref =
459 new ScopedJavaGlobalRef<jobject>();
461 num_server_thumbs_++;
464 gfx::ConvertToJavaBitmap(bitmap).obj());
469 Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
470 env, j_callback->obj(), j_bitmap_ref->obj());
473 void MostVisitedSites::RecordUMAMetrics() {
474 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumLocalThumbnailTilesHistogramName,
476 num_local_thumbs_ = 0;
477 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumEmptyTilesHistogramName, num_empty_thumbs_);
478 num_empty_thumbs_ = 0;
479 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumServerTilesHistogramName, num_server_thumbs_);
480 num_server_thumbs_ = 0;
483 static jlong Init(JNIEnv* env, jobject obj, jobject jprofile) {
484 MostVisitedSites* most_visited_sites =
485 new MostVisitedSites(ProfileAndroid::FromProfileAndroid(jprofile));
486 return reinterpret_cast<intptr_t>(most_visited_sites);