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 // Server suggestion opened, no provider.
65 const char kOpenedItemServerHistogramName
[] = "NewTabPage.MostVisited.server";
66 // Server suggestion opened with provider.
67 const char kOpenedItemServerProviderHistogramFormat
[] =
68 "NewTabPage.MostVisited.server%d";
70 const char kImpressionClientHistogramName
[] =
71 "NewTabPage.SuggestionsImpression.client";
72 // Server suggestion impression, no provider.
73 const char kImpressionServerHistogramName
[] =
74 "NewTabPage.SuggestionsImpression.server";
75 // Server suggestion impression with provider.
76 const char kImpressionServerHistogramFormat
[] =
77 "NewTabPage.SuggestionsImpression.server%d";
79 void ExtractMostVisitedTitlesAndURLs(
80 const history::MostVisitedURLList
& visited_list
,
81 std::vector
<base::string16
>* titles
,
82 std::vector
<std::string
>* urls
,
84 size_t max
= static_cast<size_t>(num_sites
);
85 for (size_t i
= 0; i
< visited_list
.size() && i
< max
; ++i
) {
86 const history::MostVisitedURL
& visited
= visited_list
[i
];
88 if (visited
.url
.is_empty())
89 break; // This is the signal that there are no more real visited sites.
91 titles
->push_back(visited
.title
);
92 urls
->push_back(visited
.url
.spec());
96 SkBitmap
ExtractThumbnail(const base::RefCountedMemory
& image_data
) {
97 scoped_ptr
<SkBitmap
> image(gfx::JPEGCodec::Decode(
100 return image
.get() ? *image
: SkBitmap();
103 void AddForcedURLOnUIThread(scoped_refptr
<history::TopSites
> top_sites
,
105 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
106 top_sites
->AddForcedURL(url
, base::Time::Now());
109 // Runs on the DB thread.
110 void GetUrlThumbnailTask(
111 std::string url_string
,
112 scoped_refptr
<TopSites
> top_sites
,
113 ScopedJavaGlobalRef
<jobject
>* j_callback
,
114 MostVisitedSites::LookupSuccessCallback lookup_success_ui_callback
,
115 base::Closure lookup_failed_ui_callback
) {
116 JNIEnv
* env
= AttachCurrentThread();
118 ScopedJavaGlobalRef
<jobject
>* j_bitmap_ref
=
119 new ScopedJavaGlobalRef
<jobject
>();
121 GURL
gurl(url_string
);
123 scoped_refptr
<base::RefCountedMemory
> data
;
124 if (top_sites
->GetPageThumbnail(gurl
, false, &data
)) {
125 SkBitmap thumbnail_bitmap
= ExtractThumbnail(*data
.get());
126 if (!thumbnail_bitmap
.empty()) {
129 gfx::ConvertToJavaBitmap(&thumbnail_bitmap
).obj());
132 // A thumbnail is not locally available for |gurl|. Make sure it is put in
133 // the list to be fetched at the next visit to this site.
134 BrowserThread::PostTask(
135 BrowserThread::UI
, FROM_HERE
,
136 base::Bind(AddForcedURLOnUIThread
, top_sites
, gurl
));
138 // If appropriate, return on the UI thread to execute the proper callback.
139 if (!lookup_failed_ui_callback
.is_null()) {
140 BrowserThread::PostTask(
141 BrowserThread::UI
, FROM_HERE
, lookup_failed_ui_callback
);
147 // Since j_callback is owned by this callback, when the callback falls out of
148 // scope it will be deleted. We need to pass ownership to the next callback.
149 ScopedJavaGlobalRef
<jobject
>* j_callback_pass
=
150 new ScopedJavaGlobalRef
<jobject
>(*j_callback
);
151 BrowserThread::PostTask(
152 BrowserThread::UI
, FROM_HERE
,
153 base::Bind(lookup_success_ui_callback
, base::Owned(j_bitmap_ref
),
154 base::Owned(j_callback_pass
)));
157 // Log an event for a given |histogram| at a given element |position|. This
158 // routine exists because regular histogram macros are cached thus can't be used
159 // if the name of the histogram will change at a given call site.
160 void LogHistogramEvent(const std::string
& histogram
, int position
,
162 base::HistogramBase
* counter
= base::LinearHistogram::FactoryGet(
167 base::Histogram::kUmaTargetedHistogramFlag
);
169 counter
->Add(position
);
172 // Return the current SyncState for use with the SuggestionsService.
173 SyncState
GetSyncState(Profile
* profile
) {
174 ProfileSyncService
* sync
=
175 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile
);
177 return SyncState::SYNC_OR_HISTORY_SYNC_DISABLED
;
178 return suggestions::GetSyncState(
179 sync
->IsSyncEnabledAndLoggedIn(),
180 sync
->SyncActive() && sync
->ConfigurationDone(),
181 sync
->GetActiveDataTypes().Has(syncer::HISTORY_DELETE_DIRECTIVES
));
186 MostVisitedSites::MostVisitedSites(Profile
* profile
)
187 : profile_(profile
), num_sites_(0), initial_load_done_(false),
188 num_local_thumbs_(0), num_server_thumbs_(0), num_empty_thumbs_(0),
189 scoped_observer_(this), weak_ptr_factory_(this) {
190 // Register the debugging page for the Suggestions Service and the thumbnails
192 content::URLDataSource::Add(profile_
,
193 new suggestions::SuggestionsSource(profile_
));
194 content::URLDataSource::Add(profile_
, new ThumbnailListSource(profile_
));
196 // Register this class as an observer to the sync service. It is important to
197 // be notified of changes in the sync state such as initialization, sync
198 // being enabled or disabled, etc.
199 ProfileSyncService
* profile_sync_service
=
200 ProfileSyncServiceFactory::GetForProfile(profile_
);
201 if (profile_sync_service
)
202 profile_sync_service
->AddObserver(this);
205 MostVisitedSites::~MostVisitedSites() {
206 ProfileSyncService
* profile_sync_service
=
207 ProfileSyncServiceFactory::GetForProfile(profile_
);
208 if (profile_sync_service
&& profile_sync_service
->HasObserver(this))
209 profile_sync_service
->RemoveObserver(this);
212 void MostVisitedSites::Destroy(JNIEnv
* env
, jobject obj
) {
216 void MostVisitedSites::OnLoadingComplete(JNIEnv
* env
, jobject obj
) {
220 void MostVisitedSites::SetMostVisitedURLsObserver(JNIEnv
* env
,
224 observer_
.Reset(env
, j_observer
);
225 num_sites_
= num_sites
;
227 QueryMostVisitedURLs();
229 scoped_refptr
<history::TopSites
> top_sites
=
230 TopSitesFactory::GetForProfile(profile_
);
232 // TopSites updates itself after a delay. To ensure up-to-date results,
233 // force an update now.
234 top_sites
->SyncWithHistory();
236 // Register as TopSitesObserver so that we can update ourselves when the
238 scoped_observer_
.Add(top_sites
.get());
242 // Called from the UI Thread.
243 void MostVisitedSites::GetURLThumbnail(JNIEnv
* env
,
246 jobject j_callback_obj
) {
247 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
248 ScopedJavaGlobalRef
<jobject
>* j_callback
=
249 new ScopedJavaGlobalRef
<jobject
>();
250 j_callback
->Reset(env
, j_callback_obj
);
252 std::string url_string
= ConvertJavaStringToUTF8(env
, url
);
253 scoped_refptr
<TopSites
> top_sites(TopSitesFactory::GetForProfile(profile_
));
255 // If the Suggestions service is enabled and in use, create a callback to
256 // fetch a server thumbnail from it, in case the local thumbnail is not found.
257 SuggestionsService
* suggestions_service
=
258 SuggestionsServiceFactory::GetForProfile(profile_
);
259 bool use_suggestions_service
= suggestions_service
&&
260 mv_source_
== SUGGESTIONS_SERVICE
;
261 base::Closure lookup_failed_callback
= use_suggestions_service
?
262 base::Bind(&MostVisitedSites::GetSuggestionsThumbnailOnUIThread
,
263 weak_ptr_factory_
.GetWeakPtr(),
264 suggestions_service
, url_string
,
265 base::Owned(new ScopedJavaGlobalRef
<jobject
>(*j_callback
))) :
267 LookupSuccessCallback lookup_success_callback
=
268 base::Bind(&MostVisitedSites::OnObtainedThumbnail
,
269 weak_ptr_factory_
.GetWeakPtr());
271 BrowserThread::PostTask(
272 BrowserThread::DB
, FROM_HERE
,
274 &GetUrlThumbnailTask
, url_string
, top_sites
,
275 base::Owned(j_callback
), lookup_success_callback
,
276 lookup_failed_callback
));
279 void MostVisitedSites::BlacklistUrl(JNIEnv
* env
,
282 std::string url
= ConvertJavaStringToUTF8(env
, j_url
);
284 switch (mv_source_
) {
286 scoped_refptr
<TopSites
> top_sites
=
287 TopSitesFactory::GetForProfile(profile_
);
289 top_sites
->AddBlacklistedURL(GURL(url
));
293 case SUGGESTIONS_SERVICE
: {
294 SuggestionsService
* suggestions_service
=
295 SuggestionsServiceFactory::GetForProfile(profile_
);
296 DCHECK(suggestions_service
);
297 suggestions_service
->BlacklistURL(
300 &MostVisitedSites::OnSuggestionsProfileAvailable
,
301 weak_ptr_factory_
.GetWeakPtr(),
302 base::Owned(new ScopedJavaGlobalRef
<jobject
>(observer_
))),
309 void MostVisitedSites::RecordOpenedMostVisitedItem(JNIEnv
* env
,
312 switch (mv_source_
) {
314 UMA_HISTOGRAM_SPARSE_SLOWLY(kOpenedItemClientHistogramName
, index
);
317 case SUGGESTIONS_SERVICE
: {
318 if (server_suggestions_
.suggestions_size() > index
) {
319 if (server_suggestions_
.suggestions(index
).providers_size()) {
320 std::string histogram
= base::StringPrintf(
321 kOpenedItemServerProviderHistogramFormat
,
322 server_suggestions_
.suggestions(index
).providers(0));
323 LogHistogramEvent(histogram
, index
, num_sites_
);
325 UMA_HISTOGRAM_SPARSE_SLOWLY(kOpenedItemServerHistogramName
, index
);
333 void MostVisitedSites::OnStateChanged() {
334 // There have been changes to the sync state. This class cares about a few
335 // (just initialized, enabled/disabled or history sync state changed). Re-run
336 // the query code which will use the proper state.
337 QueryMostVisitedURLs();
341 bool MostVisitedSites::Register(JNIEnv
* env
) {
342 return RegisterNativesImpl(env
);
345 void MostVisitedSites::QueryMostVisitedURLs() {
346 SuggestionsService
* suggestions_service
=
347 SuggestionsServiceFactory::GetForProfile(profile_
);
348 if (suggestions_service
) {
349 // Suggestions service is enabled, initiate a query.
350 suggestions_service
->FetchSuggestionsData(
351 GetSyncState(profile_
),
353 &MostVisitedSites::OnSuggestionsProfileAvailable
,
354 weak_ptr_factory_
.GetWeakPtr(),
355 base::Owned(new ScopedJavaGlobalRef
<jobject
>(observer_
))));
357 InitiateTopSitesQuery();
361 void MostVisitedSites::InitiateTopSitesQuery() {
362 scoped_refptr
<TopSites
> top_sites
= TopSitesFactory::GetForProfile(profile_
);
366 top_sites
->GetMostVisitedURLs(
368 &MostVisitedSites::OnMostVisitedURLsAvailable
,
369 weak_ptr_factory_
.GetWeakPtr(),
370 base::Owned(new ScopedJavaGlobalRef
<jobject
>(observer_
)),
375 void MostVisitedSites::OnMostVisitedURLsAvailable(
376 ScopedJavaGlobalRef
<jobject
>* j_observer
,
378 const history::MostVisitedURLList
& visited_list
) {
379 std::vector
<base::string16
> titles
;
380 std::vector
<std::string
> urls
;
381 ExtractMostVisitedTitlesAndURLs(visited_list
, &titles
, &urls
, num_sites
);
383 mv_source_
= TOP_SITES
;
385 // Only log impression metrics on the initial load of the NTP.
386 if (!initial_load_done_
) {
387 int num_tiles
= urls
.size();
388 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName
, num_tiles
);
389 for (int i
= 0; i
< num_tiles
; ++i
) {
390 UMA_HISTOGRAM_SPARSE_SLOWLY(kImpressionClientHistogramName
, i
);
393 initial_load_done_
= true;
395 JNIEnv
* env
= AttachCurrentThread();
396 Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
399 ToJavaArrayOfStrings(env
, titles
).obj(),
400 ToJavaArrayOfStrings(env
, urls
).obj());
403 void MostVisitedSites::OnSuggestionsProfileAvailable(
404 ScopedJavaGlobalRef
<jobject
>* j_observer
,
405 const SuggestionsProfile
& suggestions_profile
) {
406 int size
= suggestions_profile
.suggestions_size();
407 // With no server suggestions, fall back to local Most Visited.
409 InitiateTopSitesQuery();
413 std::vector
<base::string16
> titles
;
414 std::vector
<std::string
> urls
;
416 for (; i
< size
&& i
< num_sites_
; ++i
) {
417 const ChromeSuggestion
& suggestion
= suggestions_profile
.suggestions(i
);
418 titles
.push_back(base::UTF8ToUTF16(suggestion
.title()));
419 urls
.push_back(suggestion
.url());
420 // Only log impression metrics on the initial NTP load.
421 if (!initial_load_done_
) {
422 if (suggestion
.providers_size()) {
423 std::string histogram
= base::StringPrintf(
424 kImpressionServerHistogramFormat
, suggestion
.providers(0));
425 LogHistogramEvent(histogram
, i
, num_sites_
);
427 UMA_HISTOGRAM_SPARSE_SLOWLY(kImpressionServerHistogramName
, i
);
431 if (!initial_load_done_
) {
432 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName
, i
);
434 initial_load_done_
= true;
436 mv_source_
= SUGGESTIONS_SERVICE
;
437 // Keep a copy of the suggestions for eventual logging.
438 server_suggestions_
= suggestions_profile
;
440 JNIEnv
* env
= AttachCurrentThread();
441 Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
444 ToJavaArrayOfStrings(env
, titles
).obj(),
445 ToJavaArrayOfStrings(env
, urls
).obj());
448 void MostVisitedSites::OnObtainedThumbnail(
449 ScopedJavaGlobalRef
<jobject
>* bitmap
,
450 ScopedJavaGlobalRef
<jobject
>* j_callback
) {
451 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
452 JNIEnv
* env
= AttachCurrentThread();
458 Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
459 env
, j_callback
->obj(), bitmap
->obj());
462 void MostVisitedSites::GetSuggestionsThumbnailOnUIThread(
463 SuggestionsService
* suggestions_service
,
464 const std::string
& url_string
,
465 ScopedJavaGlobalRef
<jobject
>* j_callback
) {
466 suggestions_service
->GetPageThumbnail(
468 base::Bind(&MostVisitedSites::OnSuggestionsThumbnailAvailable
,
469 weak_ptr_factory_
.GetWeakPtr(),
470 base::Owned(new ScopedJavaGlobalRef
<jobject
>(*j_callback
))));
473 void MostVisitedSites::OnSuggestionsThumbnailAvailable(
474 ScopedJavaGlobalRef
<jobject
>* j_callback
,
476 const SkBitmap
* bitmap
) {
477 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
478 JNIEnv
* env
= AttachCurrentThread();
480 ScopedJavaGlobalRef
<jobject
>* j_bitmap_ref
=
481 new ScopedJavaGlobalRef
<jobject
>();
483 num_server_thumbs_
++;
486 gfx::ConvertToJavaBitmap(bitmap
).obj());
491 Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
492 env
, j_callback
->obj(), j_bitmap_ref
->obj());
495 void MostVisitedSites::RecordUMAMetrics() {
496 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumLocalThumbnailTilesHistogramName
,
498 num_local_thumbs_
= 0;
499 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumEmptyTilesHistogramName
, num_empty_thumbs_
);
500 num_empty_thumbs_
= 0;
501 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumServerTilesHistogramName
, num_server_thumbs_
);
502 num_server_thumbs_
= 0;
505 void MostVisitedSites::TopSitesLoaded(history::TopSites
* top_sites
) {
508 void MostVisitedSites::TopSitesChanged(history::TopSites
* top_sites
) {
509 if (mv_source_
== TOP_SITES
) {
510 // The displayed suggestions are invalidated.
511 QueryMostVisitedURLs();
515 static jlong
Init(JNIEnv
* env
, jobject obj
, jobject jprofile
) {
516 MostVisitedSites
* most_visited_sites
=
517 new MostVisitedSites(ProfileAndroid::FromProfileAndroid(jprofile
));
518 return reinterpret_cast<intptr_t>(most_visited_sites
);