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::ScopedJavaLocalRef
;
44 using base::android::ToJavaArrayOfStrings
;
45 using base::android::CheckException
;
47 using content::BrowserThread
;
48 using history::TopSites
;
49 using suggestions::ChromeSuggestion
;
50 using suggestions::SuggestionsProfile
;
51 using suggestions::SuggestionsService
;
52 using suggestions::SuggestionsServiceFactory
;
53 using suggestions::SyncState
;
57 // Total number of tiles displayed.
58 const char kNumTilesHistogramName
[] = "NewTabPage.NumberOfTiles";
59 // Tracking thumbnails.
60 const char kNumLocalThumbnailTilesHistogramName
[] =
61 "NewTabPage.NumberOfThumbnailTiles";
62 const char kNumEmptyTilesHistogramName
[] = "NewTabPage.NumberOfGrayTiles";
63 const char kNumServerTilesHistogramName
[] = "NewTabPage.NumberOfExternalTiles";
64 // Client suggestion opened.
65 const char kOpenedItemClientHistogramName
[] = "NewTabPage.MostVisited.client";
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 // 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 scoped_ptr
<SkBitmap
> MaybeFetchLocalThumbnail(
83 const scoped_refptr
<TopSites
>& top_sites
) {
84 DCHECK_CURRENTLY_ON(BrowserThread::DB
);
85 scoped_refptr
<base::RefCountedMemory
> image
;
86 scoped_ptr
<SkBitmap
> bitmap
;
87 if (top_sites
&& top_sites
->GetPageThumbnail(url
, false, &image
))
88 bitmap
.reset(gfx::JPEGCodec::Decode(image
->front(), image
->size()));
92 // Log an event for a given |histogram| at a given element |position|. This
93 // routine exists because regular histogram macros are cached thus can't be used
94 // if the name of the histogram will change at a given call site.
95 void LogHistogramEvent(const std::string
& histogram
, int position
,
97 base::HistogramBase
* counter
= base::LinearHistogram::FactoryGet(
102 base::Histogram::kUmaTargetedHistogramFlag
);
104 counter
->Add(position
);
107 // Return the current SyncState for use with the SuggestionsService.
108 SyncState
GetSyncState(Profile
* profile
) {
109 ProfileSyncService
* sync
=
110 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile
);
112 return SyncState::SYNC_OR_HISTORY_SYNC_DISABLED
;
113 return suggestions::GetSyncState(
114 sync
->CanSyncStart(),
115 sync
->IsSyncActive() && sync
->ConfigurationDone(),
116 sync
->GetActiveDataTypes().Has(syncer::HISTORY_DELETE_DIRECTIVES
));
121 MostVisitedSites::MostVisitedSites(Profile
* profile
)
122 : profile_(profile
), num_sites_(0), initial_load_done_(false),
123 num_local_thumbs_(0), num_server_thumbs_(0), num_empty_thumbs_(0),
124 scoped_observer_(this), weak_ptr_factory_(this) {
125 // Register the debugging page for the Suggestions Service and the thumbnails
127 content::URLDataSource::Add(profile_
,
128 new suggestions::SuggestionsSource(profile_
));
129 content::URLDataSource::Add(profile_
, new ThumbnailListSource(profile_
));
131 // Register this class as an observer to the sync service. It is important to
132 // be notified of changes in the sync state such as initialization, sync
133 // being enabled or disabled, etc.
134 ProfileSyncService
* profile_sync_service
=
135 ProfileSyncServiceFactory::GetForProfile(profile_
);
136 if (profile_sync_service
)
137 profile_sync_service
->AddObserver(this);
140 MostVisitedSites::~MostVisitedSites() {
141 ProfileSyncService
* profile_sync_service
=
142 ProfileSyncServiceFactory::GetForProfile(profile_
);
143 if (profile_sync_service
&& profile_sync_service
->HasObserver(this))
144 profile_sync_service
->RemoveObserver(this);
147 void MostVisitedSites::Destroy(JNIEnv
* env
, jobject obj
) {
151 void MostVisitedSites::OnLoadingComplete(JNIEnv
* env
, jobject obj
) {
155 void MostVisitedSites::SetMostVisitedURLsObserver(JNIEnv
* env
,
159 observer_
.Reset(env
, j_observer
);
160 num_sites_
= num_sites
;
162 QueryMostVisitedURLs();
164 scoped_refptr
<history::TopSites
> top_sites
=
165 TopSitesFactory::GetForProfile(profile_
);
167 // TopSites updates itself after a delay. To ensure up-to-date results,
168 // force an update now.
169 top_sites
->SyncWithHistory();
171 // Register as TopSitesObserver so that we can update ourselves when the
173 scoped_observer_
.Add(top_sites
.get());
177 void MostVisitedSites::GetURLThumbnail(JNIEnv
* env
,
180 jobject j_callback_obj
) {
181 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
182 scoped_ptr
<ScopedJavaGlobalRef
<jobject
>> j_callback(
183 new ScopedJavaGlobalRef
<jobject
>());
184 j_callback
->Reset(env
, j_callback_obj
);
186 GURL
url(ConvertJavaStringToUTF8(env
, j_url
));
187 scoped_refptr
<TopSites
> top_sites(TopSitesFactory::GetForProfile(profile_
));
189 BrowserThread::PostTaskAndReplyWithResult(
190 BrowserThread::DB
, FROM_HERE
,
191 base::Bind(&MaybeFetchLocalThumbnail
, url
, top_sites
),
192 base::Bind(&MostVisitedSites::OnLocalThumbnailFetched
,
193 weak_ptr_factory_
.GetWeakPtr(), url
,
194 base::Passed(&j_callback
)));
197 void MostVisitedSites::OnLocalThumbnailFetched(
199 scoped_ptr
<ScopedJavaGlobalRef
<jobject
>> j_callback
,
200 scoped_ptr
<SkBitmap
> bitmap
) {
201 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
203 // A thumbnail is not locally available for |url|. Make sure it is put in
204 // the list to be fetched at the next visit to this site.
205 scoped_refptr
<TopSites
> top_sites(TopSitesFactory::GetForProfile(profile_
));
207 top_sites
->AddForcedURL(url
, base::Time::Now());
208 SuggestionsService
* suggestions_service
=
209 (mv_source_
== SUGGESTIONS_SERVICE
)
210 ? SuggestionsServiceFactory::GetForProfile(profile_
)
212 if (suggestions_service
) {
213 return suggestions_service
->GetPageThumbnail(
214 url
, base::Bind(&MostVisitedSites::OnObtainedThumbnail
,
215 weak_ptr_factory_
.GetWeakPtr(), false,
216 base::Passed(&j_callback
)));
219 OnObtainedThumbnail(true, j_callback
.Pass(), url
, bitmap
.get());
222 void MostVisitedSites::OnObtainedThumbnail(
223 bool is_local_thumbnail
,
224 scoped_ptr
<ScopedJavaGlobalRef
<jobject
>> j_callback
,
226 const SkBitmap
* bitmap
) {
227 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
228 JNIEnv
* env
= AttachCurrentThread();
229 ScopedJavaLocalRef
<jobject
> j_bitmap
;
231 j_bitmap
= gfx::ConvertToJavaBitmap(bitmap
);
232 if (is_local_thumbnail
) {
235 ++num_server_thumbs_
;
240 Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
241 env
, j_callback
->obj(), j_bitmap
.obj());
244 void MostVisitedSites::BlacklistUrl(JNIEnv
* env
,
247 GURL
url(ConvertJavaStringToUTF8(env
, j_url
));
249 switch (mv_source_
) {
251 scoped_refptr
<TopSites
> top_sites
=
252 TopSitesFactory::GetForProfile(profile_
);
254 top_sites
->AddBlacklistedURL(url
);
258 case SUGGESTIONS_SERVICE
: {
259 SuggestionsService
* suggestions_service
=
260 SuggestionsServiceFactory::GetForProfile(profile_
);
261 DCHECK(suggestions_service
);
262 suggestions_service
->BlacklistURL(
263 url
, base::Bind(&MostVisitedSites::OnSuggestionsProfileAvailable
,
264 weak_ptr_factory_
.GetWeakPtr()),
271 void MostVisitedSites::RecordOpenedMostVisitedItem(JNIEnv
* env
,
274 switch (mv_source_
) {
276 UMA_HISTOGRAM_SPARSE_SLOWLY(kOpenedItemClientHistogramName
, index
);
279 case SUGGESTIONS_SERVICE
: {
280 if (server_suggestions_
.suggestions_size() > index
) {
281 if (server_suggestions_
.suggestions(index
).providers_size()) {
282 std::string histogram
= base::StringPrintf(
283 kOpenedItemServerProviderHistogramFormat
,
284 server_suggestions_
.suggestions(index
).providers(0));
285 LogHistogramEvent(histogram
, index
, num_sites_
);
287 UMA_HISTOGRAM_SPARSE_SLOWLY(kOpenedItemServerHistogramName
, index
);
295 void MostVisitedSites::OnStateChanged() {
296 // There have been changes to the sync state. This class cares about a few
297 // (just initialized, enabled/disabled or history sync state changed). Re-run
298 // the query code which will use the proper state.
299 QueryMostVisitedURLs();
303 bool MostVisitedSites::Register(JNIEnv
* env
) {
304 return RegisterNativesImpl(env
);
307 void MostVisitedSites::QueryMostVisitedURLs() {
308 SuggestionsService
* suggestions_service
=
309 SuggestionsServiceFactory::GetForProfile(profile_
);
310 if (suggestions_service
) {
311 // Suggestions service is enabled, initiate a query.
312 suggestions_service
->FetchSuggestionsData(
313 GetSyncState(profile_
),
314 base::Bind(&MostVisitedSites::OnSuggestionsProfileAvailable
,
315 weak_ptr_factory_
.GetWeakPtr()));
317 InitiateTopSitesQuery();
321 void MostVisitedSites::InitiateTopSitesQuery() {
322 scoped_refptr
<TopSites
> top_sites
= TopSitesFactory::GetForProfile(profile_
);
326 top_sites
->GetMostVisitedURLs(
327 base::Bind(&MostVisitedSites::OnMostVisitedURLsAvailable
,
328 weak_ptr_factory_
.GetWeakPtr()),
332 void MostVisitedSites::OnMostVisitedURLsAvailable(
333 const history::MostVisitedURLList
& visited_list
) {
334 std::vector
<base::string16
> titles
;
335 std::vector
<std::string
> urls
;
336 int num_tiles
= std::min(static_cast<int>(visited_list
.size()), num_sites_
);
337 for (int i
= 0; i
< num_tiles
; ++i
) {
338 const history::MostVisitedURL
& visited
= visited_list
[i
];
339 if (visited
.url
.is_empty()) {
341 break; // This is the signal that there are no more real visited sites.
343 titles
.push_back(visited
.title
);
344 urls
.push_back(visited
.url
.spec());
347 // Only log impression metrics on the initial load of the NTP.
348 if (!initial_load_done_
) {
349 for (int i
= 0; i
< num_tiles
; ++i
) {
350 UMA_HISTOGRAM_SPARSE_SLOWLY(kImpressionClientHistogramName
, i
);
353 mv_source_
= TOP_SITES
;
354 NotifyMostVisitedURLsObserver(titles
, urls
);
357 void MostVisitedSites::OnSuggestionsProfileAvailable(
358 const SuggestionsProfile
& suggestions_profile
) {
359 int num_tiles
= suggestions_profile
.suggestions_size();
360 // With no server suggestions, fall back to local Most Visited.
361 if (num_tiles
== 0) {
362 InitiateTopSitesQuery();
365 if (num_sites_
< num_tiles
)
366 num_tiles
= num_sites_
;
368 std::vector
<base::string16
> titles
;
369 std::vector
<std::string
> urls
;
370 for (int i
= 0; i
< num_tiles
; ++i
) {
371 const ChromeSuggestion
& suggestion
= suggestions_profile
.suggestions(i
);
372 titles
.push_back(base::UTF8ToUTF16(suggestion
.title()));
373 urls
.push_back(suggestion
.url());
374 // Only log impression metrics on the initial NTP load.
375 if (!initial_load_done_
) {
376 if (suggestion
.providers_size()) {
377 std::string histogram
= base::StringPrintf(
378 kImpressionServerHistogramFormat
, suggestion
.providers(0));
379 LogHistogramEvent(histogram
, i
, num_sites_
);
381 UMA_HISTOGRAM_SPARSE_SLOWLY(kImpressionServerHistogramName
, i
);
385 mv_source_
= SUGGESTIONS_SERVICE
;
386 // Keep a copy of the suggestions for eventual logging.
387 server_suggestions_
= suggestions_profile
;
388 NotifyMostVisitedURLsObserver(titles
, urls
);
391 void MostVisitedSites::NotifyMostVisitedURLsObserver(
392 const std::vector
<base::string16
>& titles
,
393 const std::vector
<std::string
>& urls
) {
394 DCHECK_EQ(titles
.size(), urls
.size());
395 if (!initial_load_done_
)
396 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName
, titles
.size());
397 initial_load_done_
= true;
398 JNIEnv
* env
= AttachCurrentThread();
399 Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
400 env
, observer_
.obj(), ToJavaArrayOfStrings(env
, titles
).obj(),
401 ToJavaArrayOfStrings(env
, urls
).obj());
404 void MostVisitedSites::RecordUMAMetrics() {
405 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumLocalThumbnailTilesHistogramName
,
407 num_local_thumbs_
= 0;
408 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumEmptyTilesHistogramName
, num_empty_thumbs_
);
409 num_empty_thumbs_
= 0;
410 UMA_HISTOGRAM_SPARSE_SLOWLY(kNumServerTilesHistogramName
, num_server_thumbs_
);
411 num_server_thumbs_
= 0;
414 void MostVisitedSites::TopSitesLoaded(history::TopSites
* top_sites
) {
417 void MostVisitedSites::TopSitesChanged(history::TopSites
* top_sites
,
418 ChangeReason change_reason
) {
419 if (mv_source_
== TOP_SITES
) {
420 // The displayed suggestions are invalidated.
421 QueryMostVisitedURLs();
425 static jlong
Init(JNIEnv
* env
, jobject obj
, jobject jprofile
) {
426 MostVisitedSites
* most_visited_sites
=
427 new MostVisitedSites(ProfileAndroid::FromProfileAndroid(jprofile
));
428 return reinterpret_cast<intptr_t>(most_visited_sites
);