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/sync/profile_sync_service.h"
28 #include "chrome/browser/sync/profile_sync_service_factory.h"
29 #include "chrome/browser/thumbnails/thumbnail_list_source.h"
30 #include "components/suggestions/suggestions_service.h"
31 #include "components/suggestions/suggestions_utils.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "content/public/browser/notification_source.h"
34 #include "content/public/browser/url_data_source.h"
35 #include "jni/MostVisitedSites_jni.h"
36 #include "third_party/skia/include/core/SkBitmap.h"
37 #include "ui/gfx/android/java_bitmap.h"
38 #include "ui/gfx/codec/jpeg_codec.h"
40 using base::android::AttachCurrentThread;
41 using base::android::ConvertUTF8ToJavaString;
42 using base::android::ConvertJavaStringToUTF8;
43 using base::android::ScopedJavaGlobalRef;
44 using base::android::ToJavaArrayOfStrings;
45 using base::android::CheckException;
46 using content::BrowserThread;
47 using history::TopSites;
48 using suggestions::ChromeSuggestion;
49 using suggestions::SuggestionsProfile;
50 using suggestions::SuggestionsService;
51 using suggestions::SuggestionsServiceFactory;
52 using suggestions::SyncState;
56 // Total number of tiles displayed.
57 const char kNumTilesHistogramName[] = "NewTabPage.NumberOfTiles";
58 // Tracking thumbnails.
59 const char kNumLocalThumbnailTilesHistogramName[] =
60 "NewTabPage.NumberOfThumbnailTiles";
61 const char kNumEmptyTilesHistogramName[] = "NewTabPage.NumberOfGrayTiles";
62 const char kNumServerTilesHistogramName[] = "NewTabPage.NumberOfExternalTiles";
63 // Client suggestion opened.
64 const char kOpenedItemClientHistogramName[] = "NewTabPage.MostVisited.client";
65 // Control group suggestion opened.
66 const char kOpenedItemControlHistogramName[] = "NewTabPage.MostVisited.client0";
67 // Server suggestion opened, no provider.
68 const char kOpenedItemServerHistogramName[] = "NewTabPage.MostVisited.server";
69 // Server suggestion opened with provider.
70 const char kOpenedItemServerProviderHistogramFormat[] =
71 "NewTabPage.MostVisited.server%d";
73 const char kImpressionClientHistogramName[] =
74 "NewTabPage.SuggestionsImpression.client";
75 // Control group impression.
76 const char kImpressionControlHistogramName[] =
77 "NewTabPage.SuggestionsImpression.client0";
78 // Server suggestion impression, no provider.
79 const char kImpressionServerHistogramName[] =
80 "NewTabPage.SuggestionsImpression.server";
81 // Server suggestion impression with provider.
82 const char kImpressionServerHistogramFormat[] =
83 "NewTabPage.SuggestionsImpression.server%d";
85 void ExtractMostVisitedTitlesAndURLs(
86 const history::MostVisitedURLList& visited_list,
87 std::vector<base::string16>* titles,
88 std::vector<std::string>* urls,
90 size_t max = static_cast<size_t>(num_sites);
91 for (size_t i = 0; i < visited_list.size() && i < max; ++i) {
92 const history::MostVisitedURL& visited = visited_list[i];
94 if (visited.url.is_empty())
95 break; // This is the signal that there are no more real visited sites.
97 titles->push_back(visited.title);
98 urls->push_back(visited.url.spec());
102 SkBitmap ExtractThumbnail(const base::RefCountedMemory& image_data) {
103 scoped_ptr<SkBitmap> image(gfx::JPEGCodec::Decode(
106 return image.get() ? *image : SkBitmap();
109 void AddForcedURLOnUIThread(scoped_refptr<history::TopSites> top_sites,
111 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
112 top_sites->AddForcedURL(url, base::Time::Now());
115 // Runs on the DB thread.
116 void GetUrlThumbnailTask(
117 std::string url_string,
118 scoped_refptr<TopSites> top_sites,
119 ScopedJavaGlobalRef<jobject>* j_callback,
120 MostVisitedSites::LookupSuccessCallback lookup_success_ui_callback,
121 base::Closure lookup_failed_ui_callback) {
122 JNIEnv* env = AttachCurrentThread();
124 ScopedJavaGlobalRef<jobject>* j_bitmap_ref =
125 new ScopedJavaGlobalRef<jobject>();
127 GURL gurl(url_string);
129 scoped_refptr<base::RefCountedMemory> data;
130 if (top_sites->GetPageThumbnail(gurl, false, &data)) {
131 SkBitmap thumbnail_bitmap = ExtractThumbnail(*data.get());
132 if (!thumbnail_bitmap.empty()) {
135 gfx::ConvertToJavaBitmap(&thumbnail_bitmap).obj());
138 // A thumbnail is not locally available for |gurl|. Make sure it is put in
139 // the list to be fetched at the next visit to this site.
140 BrowserThread::PostTask(
141 BrowserThread::UI, FROM_HERE,
142 base::Bind(AddForcedURLOnUIThread, top_sites, gurl));
144 // If appropriate, return on the UI thread to execute the proper callback.
145 if (!lookup_failed_ui_callback.is_null()) {
146 BrowserThread::PostTask(
147 BrowserThread::UI, FROM_HERE, lookup_failed_ui_callback);
153 // Since j_callback is owned by this callback, when the callback falls out of
154 // scope it will be deleted. We need to pass ownership to the next callback.
155 ScopedJavaGlobalRef<jobject>* j_callback_pass =
156 new ScopedJavaGlobalRef<jobject>(*j_callback);
157 BrowserThread::PostTask(
158 BrowserThread::UI, FROM_HERE,
159 base::Bind(lookup_success_ui_callback, base::Owned(j_bitmap_ref),
160 base::Owned(j_callback_pass)));
163 // Log an event for a given |histogram| at a given element |position|. This
164 // routine exists because regular histogram macros are cached thus can't be used
165 // if the name of the histogram will change at a given call site.
166 void LogHistogramEvent(const std::string& histogram, int position,
168 base::HistogramBase* counter = base::LinearHistogram::FactoryGet(
173 base::Histogram::kUmaTargetedHistogramFlag);
175 counter->Add(position);
178 // Return the current SyncState for use with the SuggestionsService.
179 SyncState GetSyncState(Profile* profile) {
180 ProfileSyncService* sync =
181 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
183 return SyncState::SYNC_OR_HISTORY_SYNC_DISABLED;
184 return suggestions::GetSyncState(
185 sync->IsSyncEnabledAndLoggedIn(),
187 sync->GetActiveDataTypes().Has(syncer::HISTORY_DELETE_DIRECTIVES));
192 MostVisitedSites::MostVisitedSites(Profile* profile)
193 : profile_(profile), num_sites_(0), is_control_group_(false),
194 initial_load_done_(false), num_local_thumbs_(0), num_server_thumbs_(0),
195 num_empty_thumbs_(0), weak_ptr_factory_(this) {
196 // Register the debugging page for the Suggestions Service and the thumbnails
198 content::URLDataSource::Add(profile_,
199 new suggestions::SuggestionsSource(profile_));
200 content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_));
202 // Register this class as an observer to the sync service. It is important to
203 // be notified of changes in the sync state such as initialization, sync
204 // being enabled or disabled, etc.
205 ProfileSyncService* profile_sync_service =
206 ProfileSyncServiceFactory::GetForProfile(profile_);
207 if (profile_sync_service)
208 profile_sync_service->AddObserver(this);
211 MostVisitedSites::~MostVisitedSites() {
212 ProfileSyncService* profile_sync_service =
213 ProfileSyncServiceFactory::GetForProfile(profile_);
214 if (profile_sync_service && profile_sync_service->HasObserver(this))
215 profile_sync_service->RemoveObserver(this);
218 void MostVisitedSites::Destroy(JNIEnv* env, jobject obj) {
222 void MostVisitedSites::OnLoadingComplete(JNIEnv* env, jobject obj) {
226 void MostVisitedSites::SetMostVisitedURLsObserver(JNIEnv* env,
230 observer_.Reset(env, j_observer);
231 num_sites_ = num_sites;
233 QueryMostVisitedURLs();
235 history::TopSites* top_sites = profile_->GetTopSites();
237 // TopSites updates itself after a delay. To ensure up-to-date results,
238 // force an update now.
239 top_sites->SyncWithHistory();
241 // Register for notification when TopSites changes so that we can update
243 registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED,
244 content::Source<history::TopSites>(top_sites));
248 // Called from the UI Thread.
249 void MostVisitedSites::GetURLThumbnail(JNIEnv* env,
252 jobject j_callback_obj) {
253 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
254 ScopedJavaGlobalRef<jobject>* j_callback =
255 new ScopedJavaGlobalRef<jobject>();
256 j_callback->Reset(env, j_callback_obj);
258 std::string url_string = ConvertJavaStringToUTF8(env, url);
259 scoped_refptr<TopSites> top_sites(profile_->GetTopSites());
261 // If the Suggestions service is enabled and in use, create a callback to
262 // fetch a server thumbnail from it, in case the local thumbnail is not found.
263 SuggestionsService* suggestions_service =
264 SuggestionsServiceFactory::GetForProfile(profile_);
265 bool use_suggestions_service = suggestions_service &&
266 mv_source_ == SUGGESTIONS_SERVICE;
267 base::Closure lookup_failed_callback = use_suggestions_service ?
268 base::Bind(&MostVisitedSites::GetSuggestionsThumbnailOnUIThread,
269 weak_ptr_factory_.GetWeakPtr(),
270 suggestions_service, url_string,
271 base::Owned(new ScopedJavaGlobalRef<jobject>(*j_callback))) :
273 LookupSuccessCallback lookup_success_callback =
274 base::Bind(&MostVisitedSites::OnObtainedThumbnail,
275 weak_ptr_factory_.GetWeakPtr());
277 BrowserThread::PostTask(
278 BrowserThread::DB, FROM_HERE,
280 &GetUrlThumbnailTask, url_string, top_sites,
281 base::Owned(j_callback), lookup_success_callback,
282 lookup_failed_callback));
285 void MostVisitedSites::BlacklistUrl(JNIEnv* env,
288 std::string url = ConvertJavaStringToUTF8(env, j_url);
290 switch (mv_source_) {
292 TopSites* top_sites = profile_->GetTopSites();
294 top_sites->AddBlacklistedURL(GURL(url));
298 case SUGGESTIONS_SERVICE: {
299 SuggestionsService* suggestions_service =
300 SuggestionsServiceFactory::GetForProfile(profile_);
301 DCHECK(suggestions_service);
302 suggestions_service->BlacklistURL(
305 &MostVisitedSites::OnSuggestionsProfileAvailable,
306 weak_ptr_factory_.GetWeakPtr(),
307 base::Owned(new ScopedJavaGlobalRef<jobject>(observer_))));
313 void MostVisitedSites::RecordOpenedMostVisitedItem(JNIEnv* env,
316 switch (mv_source_) {
318 const std::string histogram = is_control_group_ ?
319 kOpenedItemControlHistogramName : kOpenedItemClientHistogramName;
320 LogHistogramEvent(histogram, index, num_sites_);
323 case SUGGESTIONS_SERVICE: {
324 if (server_suggestions_.suggestions_size() > index) {
325 if (server_suggestions_.suggestions(index).providers_size()) {
326 std::string histogram = base::StringPrintf(
327 kOpenedItemServerProviderHistogramFormat,
328 server_suggestions_.suggestions(index).providers(0));
329 LogHistogramEvent(histogram, index, num_sites_);
331 UMA_HISTOGRAM_SPARSE_SLOWLY(kOpenedItemServerHistogramName, index);
339 void MostVisitedSites::Observe(int type,
340 const content::NotificationSource& source,
341 const content::NotificationDetails& details) {
342 DCHECK_EQ(type, chrome::NOTIFICATION_TOP_SITES_CHANGED);
344 if (mv_source_ == TOP_SITES) {
345 // The displayed suggestions are invalidated.
346 QueryMostVisitedURLs();
350 void MostVisitedSites::OnStateChanged() {
351 // There have been changes to the sync state. This class cares about a few
352 // (just initialized, enabled/disabled or history sync state changed). Re-run
353 // the query code which will use the proper state.
354 QueryMostVisitedURLs();
358 bool MostVisitedSites::Register(JNIEnv* env) {
359 return RegisterNativesImpl(env);
362 void MostVisitedSites::QueryMostVisitedURLs() {
363 SuggestionsService* suggestions_service =
364 SuggestionsServiceFactory::GetForProfile(profile_);
365 if (suggestions_service) {
366 // Suggestions service is enabled, initiate a query.
367 suggestions_service->FetchSuggestionsData(
368 GetSyncState(profile_),
370 &MostVisitedSites::OnSuggestionsProfileAvailable,
371 weak_ptr_factory_.GetWeakPtr(),
372 base::Owned(new ScopedJavaGlobalRef<jobject>(observer_))));
374 InitiateTopSitesQuery();
378 void MostVisitedSites::InitiateTopSitesQuery() {
379 TopSites* top_sites = profile_->GetTopSites();
383 top_sites->GetMostVisitedURLs(
385 &MostVisitedSites::OnMostVisitedURLsAvailable,
386 weak_ptr_factory_.GetWeakPtr(),
387 base::Owned(new ScopedJavaGlobalRef<jobject>(observer_)),
392 void MostVisitedSites::OnMostVisitedURLsAvailable(
393 ScopedJavaGlobalRef<jobject>* j_observer,
395 const history::MostVisitedURLList& visited_list) {
396 std::vector<base::string16> titles;
397 std::vector<std::string> urls;
398 ExtractMostVisitedTitlesAndURLs(visited_list, &titles, &urls, num_sites);
400 mv_source_ = TOP_SITES;
402 // Only log impression metrics on the initial load of the NTP.
403 if (!initial_load_done_) {
404 int num_tiles = urls.size();
405 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName, num_tiles);
406 const std::string histogram = is_control_group_ ?
407 kImpressionControlHistogramName : kImpressionClientHistogramName;
408 for (int i = 0; i < num_tiles; ++i) {
409 LogHistogramEvent(histogram, i, num_sites_);
412 initial_load_done_ = true;
414 JNIEnv* env = AttachCurrentThread();
415 Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
418 ToJavaArrayOfStrings(env, titles).obj(),
419 ToJavaArrayOfStrings(env, urls).obj());
422 void MostVisitedSites::OnSuggestionsProfileAvailable(
423 ScopedJavaGlobalRef<jobject>* j_observer,
424 const SuggestionsProfile& suggestions_profile) {
425 int size = suggestions_profile.suggestions_size();
427 // Determine if the user is in a control group (they would have received
428 // suggestions, but are in a group where they shouldn't).
429 is_control_group_ = size && SuggestionsService::IsControlGroup();
431 // If no suggestions data is available or the user is in a control group,
432 // initiate Top Sites query.
433 if (is_control_group_ || !size) {
434 InitiateTopSitesQuery();
438 std::vector<base::string16> titles;
439 std::vector<std::string> urls;
442 for (; i < size && i < num_sites_; ++i) {
443 const ChromeSuggestion& suggestion = suggestions_profile.suggestions(i);
444 titles.push_back(base::UTF8ToUTF16(suggestion.title()));
445 urls.push_back(suggestion.url());
446 // Only log impression metrics on the initial NTP load.
447 if (!initial_load_done_) {
448 if (suggestion.providers_size()) {
449 std::string histogram = base::StringPrintf(
450 kImpressionServerHistogramFormat, suggestion.providers(0));
451 LogHistogramEvent(histogram, i, num_sites_);
453 UMA_HISTOGRAM_SPARSE_SLOWLY(kImpressionServerHistogramName, i);
457 if (!initial_load_done_) {
458 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName, i);
460 initial_load_done_ = true;
462 mv_source_ = SUGGESTIONS_SERVICE;
463 // Keep a copy of the suggestions for eventual logging.
464 server_suggestions_ = suggestions_profile;
466 JNIEnv* env = AttachCurrentThread();
467 Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
470 ToJavaArrayOfStrings(env, titles).obj(),
471 ToJavaArrayOfStrings(env, urls).obj());
474 void MostVisitedSites::OnObtainedThumbnail(
475 ScopedJavaGlobalRef<jobject>* bitmap,
476 ScopedJavaGlobalRef<jobject>* j_callback) {
477 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
478 JNIEnv* env = AttachCurrentThread();
484 Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
485 env, j_callback->obj(), bitmap->obj());
488 void MostVisitedSites::GetSuggestionsThumbnailOnUIThread(
489 SuggestionsService* suggestions_service,
490 const std::string& url_string,
491 ScopedJavaGlobalRef<jobject>* j_callback) {
492 suggestions_service->GetPageThumbnail(
494 base::Bind(&MostVisitedSites::OnSuggestionsThumbnailAvailable,
495 weak_ptr_factory_.GetWeakPtr(),
496 base::Owned(new ScopedJavaGlobalRef<jobject>(*j_callback))));
499 void MostVisitedSites::OnSuggestionsThumbnailAvailable(
500 ScopedJavaGlobalRef<jobject>* j_callback,
502 const SkBitmap* bitmap) {
503 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
504 JNIEnv* env = AttachCurrentThread();
506 ScopedJavaGlobalRef<jobject>* j_bitmap_ref =
507 new ScopedJavaGlobalRef<jobject>();
509 num_server_thumbs_++;
512 gfx::ConvertToJavaBitmap(bitmap).obj());
517 Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
518 env, j_callback->obj(), j_bitmap_ref->obj());
521 void MostVisitedSites::RecordUMAMetrics() {
522 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumLocalThumbnailTilesHistogramName,
524 num_local_thumbs_ = 0;
525 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumEmptyTilesHistogramName, num_empty_thumbs_);
526 num_empty_thumbs_ = 0;
527 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumServerTilesHistogramName, num_server_thumbs_);
528 num_server_thumbs_ = 0;
531 static jlong Init(JNIEnv* env, jobject obj, jobject jprofile) {
532 MostVisitedSites* most_visited_sites =
533 new MostVisitedSites(ProfileAndroid::FromProfileAndroid(jprofile));
534 return reinterpret_cast<intptr_t>(most_visited_sites);