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/history/top_sites_factory.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/profiles/profile_android.h"
24 #include "chrome/browser/search/suggestions/suggestions_service_factory.h"
25 #include "chrome/browser/search/suggestions/suggestions_source.h"
26 #include "chrome/browser/sync/profile_sync_service.h"
27 #include "chrome/browser/sync/profile_sync_service_factory.h"
28 #include "chrome/browser/thumbnails/thumbnail_list_source.h"
29 #include "components/history/core/browser/top_sites.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/url_data_source.h"
34 #include "jni/MostVisitedSites_jni.h"
35 #include "third_party/skia/include/core/SkBitmap.h"
36 #include "ui/gfx/android/java_bitmap.h"
37 #include "ui/gfx/codec/jpeg_codec.h"
39 using base::android::AttachCurrentThread
;
40 using base::android::ConvertUTF8ToJavaString
;
41 using base::android::ConvertJavaStringToUTF8
;
42 using base::android::ScopedJavaGlobalRef
;
43 using base::android::ToJavaArrayOfStrings
;
44 using base::android::CheckException
;
45 using content::BrowserThread
;
46 using history::TopSites
;
47 using suggestions::ChromeSuggestion
;
48 using suggestions::SuggestionsProfile
;
49 using suggestions::SuggestionsService
;
50 using suggestions::SuggestionsServiceFactory
;
51 using suggestions::SyncState
;
55 // Total number of tiles displayed.
56 const char kNumTilesHistogramName
[] = "NewTabPage.NumberOfTiles";
57 // Tracking thumbnails.
58 const char kNumLocalThumbnailTilesHistogramName
[] =
59 "NewTabPage.NumberOfThumbnailTiles";
60 const char kNumEmptyTilesHistogramName
[] = "NewTabPage.NumberOfGrayTiles";
61 const char kNumServerTilesHistogramName
[] = "NewTabPage.NumberOfExternalTiles";
62 // Client suggestion opened.
63 const char kOpenedItemClientHistogramName
[] = "NewTabPage.MostVisited.client";
64 // Control group suggestion opened.
65 const char kOpenedItemControlHistogramName
[] = "NewTabPage.MostVisited.client0";
66 // Server suggestion opened, no provider.
67 const char kOpenedItemServerHistogramName
[] = "NewTabPage.MostVisited.server";
68 // Server suggestion opened with provider.
69 const char kOpenedItemServerProviderHistogramFormat
[] =
70 "NewTabPage.MostVisited.server%d";
72 const char kImpressionClientHistogramName
[] =
73 "NewTabPage.SuggestionsImpression.client";
74 // Control group impression.
75 const char kImpressionControlHistogramName
[] =
76 "NewTabPage.SuggestionsImpression.client0";
77 // Server suggestion impression, no provider.
78 const char kImpressionServerHistogramName
[] =
79 "NewTabPage.SuggestionsImpression.server";
80 // Server suggestion impression with provider.
81 const char kImpressionServerHistogramFormat
[] =
82 "NewTabPage.SuggestionsImpression.server%d";
84 void ExtractMostVisitedTitlesAndURLs(
85 const history::MostVisitedURLList
& visited_list
,
86 std::vector
<base::string16
>* titles
,
87 std::vector
<std::string
>* urls
,
89 size_t max
= static_cast<size_t>(num_sites
);
90 for (size_t i
= 0; i
< visited_list
.size() && i
< max
; ++i
) {
91 const history::MostVisitedURL
& visited
= visited_list
[i
];
93 if (visited
.url
.is_empty())
94 break; // This is the signal that there are no more real visited sites.
96 titles
->push_back(visited
.title
);
97 urls
->push_back(visited
.url
.spec());
101 SkBitmap
ExtractThumbnail(const base::RefCountedMemory
& image_data
) {
102 scoped_ptr
<SkBitmap
> image(gfx::JPEGCodec::Decode(
105 return image
.get() ? *image
: SkBitmap();
108 void AddForcedURLOnUIThread(scoped_refptr
<history::TopSites
> top_sites
,
110 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
111 top_sites
->AddForcedURL(url
, base::Time::Now());
114 // Runs on the DB thread.
115 void GetUrlThumbnailTask(
116 std::string url_string
,
117 scoped_refptr
<TopSites
> top_sites
,
118 ScopedJavaGlobalRef
<jobject
>* j_callback
,
119 MostVisitedSites::LookupSuccessCallback lookup_success_ui_callback
,
120 base::Closure lookup_failed_ui_callback
) {
121 JNIEnv
* env
= AttachCurrentThread();
123 ScopedJavaGlobalRef
<jobject
>* j_bitmap_ref
=
124 new ScopedJavaGlobalRef
<jobject
>();
126 GURL
gurl(url_string
);
128 scoped_refptr
<base::RefCountedMemory
> data
;
129 if (top_sites
->GetPageThumbnail(gurl
, false, &data
)) {
130 SkBitmap thumbnail_bitmap
= ExtractThumbnail(*data
.get());
131 if (!thumbnail_bitmap
.empty()) {
134 gfx::ConvertToJavaBitmap(&thumbnail_bitmap
).obj());
137 // A thumbnail is not locally available for |gurl|. Make sure it is put in
138 // the list to be fetched at the next visit to this site.
139 BrowserThread::PostTask(
140 BrowserThread::UI
, FROM_HERE
,
141 base::Bind(AddForcedURLOnUIThread
, top_sites
, gurl
));
143 // If appropriate, return on the UI thread to execute the proper callback.
144 if (!lookup_failed_ui_callback
.is_null()) {
145 BrowserThread::PostTask(
146 BrowserThread::UI
, FROM_HERE
, lookup_failed_ui_callback
);
152 // Since j_callback is owned by this callback, when the callback falls out of
153 // scope it will be deleted. We need to pass ownership to the next callback.
154 ScopedJavaGlobalRef
<jobject
>* j_callback_pass
=
155 new ScopedJavaGlobalRef
<jobject
>(*j_callback
);
156 BrowserThread::PostTask(
157 BrowserThread::UI
, FROM_HERE
,
158 base::Bind(lookup_success_ui_callback
, base::Owned(j_bitmap_ref
),
159 base::Owned(j_callback_pass
)));
162 // Log an event for a given |histogram| at a given element |position|. This
163 // routine exists because regular histogram macros are cached thus can't be used
164 // if the name of the histogram will change at a given call site.
165 void LogHistogramEvent(const std::string
& histogram
, int position
,
167 base::HistogramBase
* counter
= base::LinearHistogram::FactoryGet(
172 base::Histogram::kUmaTargetedHistogramFlag
);
174 counter
->Add(position
);
177 // Return the current SyncState for use with the SuggestionsService.
178 SyncState
GetSyncState(Profile
* profile
) {
179 ProfileSyncService
* sync
=
180 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile
);
182 return SyncState::SYNC_OR_HISTORY_SYNC_DISABLED
;
183 return suggestions::GetSyncState(
184 sync
->IsSyncEnabledAndLoggedIn(),
185 sync
->SyncActive() && sync
->ConfigurationDone(),
186 sync
->GetActiveDataTypes().Has(syncer::HISTORY_DELETE_DIRECTIVES
));
191 MostVisitedSites::MostVisitedSites(Profile
* profile
)
192 : profile_(profile
), num_sites_(0), is_control_group_(false),
193 initial_load_done_(false), num_local_thumbs_(0), num_server_thumbs_(0),
194 num_empty_thumbs_(0), scoped_observer_(this), weak_ptr_factory_(this) {
195 // Register the debugging page for the Suggestions Service and the thumbnails
197 content::URLDataSource::Add(profile_
,
198 new suggestions::SuggestionsSource(profile_
));
199 content::URLDataSource::Add(profile_
, new ThumbnailListSource(profile_
));
201 // Register this class as an observer to the sync service. It is important to
202 // be notified of changes in the sync state such as initialization, sync
203 // being enabled or disabled, etc.
204 ProfileSyncService
* profile_sync_service
=
205 ProfileSyncServiceFactory::GetForProfile(profile_
);
206 if (profile_sync_service
)
207 profile_sync_service
->AddObserver(this);
210 MostVisitedSites::~MostVisitedSites() {
211 ProfileSyncService
* profile_sync_service
=
212 ProfileSyncServiceFactory::GetForProfile(profile_
);
213 if (profile_sync_service
&& profile_sync_service
->HasObserver(this))
214 profile_sync_service
->RemoveObserver(this);
217 void MostVisitedSites::Destroy(JNIEnv
* env
, jobject obj
) {
221 void MostVisitedSites::OnLoadingComplete(JNIEnv
* env
, jobject obj
) {
225 void MostVisitedSites::SetMostVisitedURLsObserver(JNIEnv
* env
,
229 observer_
.Reset(env
, j_observer
);
230 num_sites_
= num_sites
;
232 QueryMostVisitedURLs();
234 scoped_refptr
<history::TopSites
> top_sites
=
235 TopSitesFactory::GetForProfile(profile_
);
237 // TopSites updates itself after a delay. To ensure up-to-date results,
238 // force an update now.
239 top_sites
->SyncWithHistory();
241 // Register as TopSitesObserver so that we can update ourselves when the
243 scoped_observer_
.Add(top_sites
.get());
247 // Called from the UI Thread.
248 void MostVisitedSites::GetURLThumbnail(JNIEnv
* env
,
251 jobject j_callback_obj
) {
252 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
253 ScopedJavaGlobalRef
<jobject
>* j_callback
=
254 new ScopedJavaGlobalRef
<jobject
>();
255 j_callback
->Reset(env
, j_callback_obj
);
257 std::string url_string
= ConvertJavaStringToUTF8(env
, url
);
258 scoped_refptr
<TopSites
> top_sites(TopSitesFactory::GetForProfile(profile_
));
260 // If the Suggestions service is enabled and in use, create a callback to
261 // fetch a server thumbnail from it, in case the local thumbnail is not found.
262 SuggestionsService
* suggestions_service
=
263 SuggestionsServiceFactory::GetForProfile(profile_
);
264 bool use_suggestions_service
= suggestions_service
&&
265 mv_source_
== SUGGESTIONS_SERVICE
;
266 base::Closure lookup_failed_callback
= use_suggestions_service
?
267 base::Bind(&MostVisitedSites::GetSuggestionsThumbnailOnUIThread
,
268 weak_ptr_factory_
.GetWeakPtr(),
269 suggestions_service
, url_string
,
270 base::Owned(new ScopedJavaGlobalRef
<jobject
>(*j_callback
))) :
272 LookupSuccessCallback lookup_success_callback
=
273 base::Bind(&MostVisitedSites::OnObtainedThumbnail
,
274 weak_ptr_factory_
.GetWeakPtr());
276 BrowserThread::PostTask(
277 BrowserThread::DB
, FROM_HERE
,
279 &GetUrlThumbnailTask
, url_string
, top_sites
,
280 base::Owned(j_callback
), lookup_success_callback
,
281 lookup_failed_callback
));
284 void MostVisitedSites::BlacklistUrl(JNIEnv
* env
,
287 std::string url
= ConvertJavaStringToUTF8(env
, j_url
);
289 switch (mv_source_
) {
291 scoped_refptr
<TopSites
> top_sites
=
292 TopSitesFactory::GetForProfile(profile_
);
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_
))),
314 void MostVisitedSites::RecordOpenedMostVisitedItem(JNIEnv
* env
,
317 switch (mv_source_
) {
319 const std::string histogram
= is_control_group_
?
320 kOpenedItemControlHistogramName
: kOpenedItemClientHistogramName
;
321 LogHistogramEvent(histogram
, index
, num_sites_
);
324 case SUGGESTIONS_SERVICE
: {
325 if (server_suggestions_
.suggestions_size() > index
) {
326 if (server_suggestions_
.suggestions(index
).providers_size()) {
327 std::string histogram
= base::StringPrintf(
328 kOpenedItemServerProviderHistogramFormat
,
329 server_suggestions_
.suggestions(index
).providers(0));
330 LogHistogramEvent(histogram
, index
, num_sites_
);
332 UMA_HISTOGRAM_SPARSE_SLOWLY(kOpenedItemServerHistogramName
, index
);
340 void MostVisitedSites::OnStateChanged() {
341 // There have been changes to the sync state. This class cares about a few
342 // (just initialized, enabled/disabled or history sync state changed). Re-run
343 // the query code which will use the proper state.
344 QueryMostVisitedURLs();
348 bool MostVisitedSites::Register(JNIEnv
* env
) {
349 return RegisterNativesImpl(env
);
352 void MostVisitedSites::QueryMostVisitedURLs() {
353 SuggestionsService
* suggestions_service
=
354 SuggestionsServiceFactory::GetForProfile(profile_
);
355 if (suggestions_service
) {
356 // Suggestions service is enabled, initiate a query.
357 suggestions_service
->FetchSuggestionsData(
358 GetSyncState(profile_
),
360 &MostVisitedSites::OnSuggestionsProfileAvailable
,
361 weak_ptr_factory_
.GetWeakPtr(),
362 base::Owned(new ScopedJavaGlobalRef
<jobject
>(observer_
))));
364 InitiateTopSitesQuery();
368 void MostVisitedSites::InitiateTopSitesQuery() {
369 scoped_refptr
<TopSites
> top_sites
= TopSitesFactory::GetForProfile(profile_
);
373 top_sites
->GetMostVisitedURLs(
375 &MostVisitedSites::OnMostVisitedURLsAvailable
,
376 weak_ptr_factory_
.GetWeakPtr(),
377 base::Owned(new ScopedJavaGlobalRef
<jobject
>(observer_
)),
382 void MostVisitedSites::OnMostVisitedURLsAvailable(
383 ScopedJavaGlobalRef
<jobject
>* j_observer
,
385 const history::MostVisitedURLList
& visited_list
) {
386 std::vector
<base::string16
> titles
;
387 std::vector
<std::string
> urls
;
388 ExtractMostVisitedTitlesAndURLs(visited_list
, &titles
, &urls
, num_sites
);
390 mv_source_
= TOP_SITES
;
392 // Only log impression metrics on the initial load of the NTP.
393 if (!initial_load_done_
) {
394 int num_tiles
= urls
.size();
395 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName
, num_tiles
);
396 const std::string histogram
= is_control_group_
?
397 kImpressionControlHistogramName
: kImpressionClientHistogramName
;
398 for (int i
= 0; i
< num_tiles
; ++i
) {
399 LogHistogramEvent(histogram
, i
, num_sites_
);
402 initial_load_done_
= true;
404 JNIEnv
* env
= AttachCurrentThread();
405 Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
408 ToJavaArrayOfStrings(env
, titles
).obj(),
409 ToJavaArrayOfStrings(env
, urls
).obj());
412 void MostVisitedSites::OnSuggestionsProfileAvailable(
413 ScopedJavaGlobalRef
<jobject
>* j_observer
,
414 const SuggestionsProfile
& suggestions_profile
) {
415 int size
= suggestions_profile
.suggestions_size();
417 // Determine if the user is in a control group (they would have received
418 // suggestions, but are in a group where they shouldn't).
419 is_control_group_
= size
&& SuggestionsService::IsControlGroup();
421 // If no suggestions data is available or the user is in a control group,
422 // initiate Top Sites query.
423 if (is_control_group_
|| !size
) {
424 InitiateTopSitesQuery();
428 std::vector
<base::string16
> titles
;
429 std::vector
<std::string
> urls
;
432 for (; i
< size
&& i
< num_sites_
; ++i
) {
433 const ChromeSuggestion
& suggestion
= suggestions_profile
.suggestions(i
);
434 titles
.push_back(base::UTF8ToUTF16(suggestion
.title()));
435 urls
.push_back(suggestion
.url());
436 // Only log impression metrics on the initial NTP load.
437 if (!initial_load_done_
) {
438 if (suggestion
.providers_size()) {
439 std::string histogram
= base::StringPrintf(
440 kImpressionServerHistogramFormat
, suggestion
.providers(0));
441 LogHistogramEvent(histogram
, i
, num_sites_
);
443 UMA_HISTOGRAM_SPARSE_SLOWLY(kImpressionServerHistogramName
, i
);
447 if (!initial_load_done_
) {
448 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName
, i
);
450 initial_load_done_
= true;
452 mv_source_
= SUGGESTIONS_SERVICE
;
453 // Keep a copy of the suggestions for eventual logging.
454 server_suggestions_
= suggestions_profile
;
456 JNIEnv
* env
= AttachCurrentThread();
457 Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
460 ToJavaArrayOfStrings(env
, titles
).obj(),
461 ToJavaArrayOfStrings(env
, urls
).obj());
464 void MostVisitedSites::OnObtainedThumbnail(
465 ScopedJavaGlobalRef
<jobject
>* bitmap
,
466 ScopedJavaGlobalRef
<jobject
>* j_callback
) {
467 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
468 JNIEnv
* env
= AttachCurrentThread();
474 Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
475 env
, j_callback
->obj(), bitmap
->obj());
478 void MostVisitedSites::GetSuggestionsThumbnailOnUIThread(
479 SuggestionsService
* suggestions_service
,
480 const std::string
& url_string
,
481 ScopedJavaGlobalRef
<jobject
>* j_callback
) {
482 suggestions_service
->GetPageThumbnail(
484 base::Bind(&MostVisitedSites::OnSuggestionsThumbnailAvailable
,
485 weak_ptr_factory_
.GetWeakPtr(),
486 base::Owned(new ScopedJavaGlobalRef
<jobject
>(*j_callback
))));
489 void MostVisitedSites::OnSuggestionsThumbnailAvailable(
490 ScopedJavaGlobalRef
<jobject
>* j_callback
,
492 const SkBitmap
* bitmap
) {
493 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
494 JNIEnv
* env
= AttachCurrentThread();
496 ScopedJavaGlobalRef
<jobject
>* j_bitmap_ref
=
497 new ScopedJavaGlobalRef
<jobject
>();
499 num_server_thumbs_
++;
502 gfx::ConvertToJavaBitmap(bitmap
).obj());
507 Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
508 env
, j_callback
->obj(), j_bitmap_ref
->obj());
511 void MostVisitedSites::RecordUMAMetrics() {
512 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumLocalThumbnailTilesHistogramName
,
514 num_local_thumbs_
= 0;
515 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumEmptyTilesHistogramName
, num_empty_thumbs_
);
516 num_empty_thumbs_
= 0;
517 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumServerTilesHistogramName
, num_server_thumbs_
);
518 num_server_thumbs_
= 0;
521 void MostVisitedSites::TopSitesLoaded(history::TopSites
* top_sites
) {
524 void MostVisitedSites::TopSitesChanged(history::TopSites
* top_sites
) {
525 if (mv_source_
== TOP_SITES
) {
526 // The displayed suggestions are invalidated.
527 QueryMostVisitedURLs();
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
);