Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / android / most_visited_sites.cc
blob24231ab37a5dffcdad1a5f33503ae886e7ea04c8
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"
7 #include <string>
8 #include <vector>
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;
46 using base::WeakPtr;
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;
55 namespace {
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";
71 // Client impression.
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(
82 const GURL& url,
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()));
89 return bitmap.Pass();
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,
96 int num_sites) {
97 base::HistogramBase* counter = base::LinearHistogram::FactoryGet(
98 histogram,
100 num_sites,
101 num_sites + 1,
102 base::Histogram::kUmaTargetedHistogramFlag);
103 if (counter)
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);
111 if (!sync)
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));
119 } // namespace
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
126 // debugging page.
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) {
148 delete this;
151 void MostVisitedSites::OnLoadingComplete(JNIEnv* env, jobject obj) {
152 RecordUMAMetrics();
155 void MostVisitedSites::SetMostVisitedURLsObserver(JNIEnv* env,
156 jobject obj,
157 jobject j_observer,
158 jint num_sites) {
159 observer_.Reset(env, j_observer);
160 num_sites_ = num_sites;
162 QueryMostVisitedURLs();
164 scoped_refptr<history::TopSites> top_sites =
165 TopSitesFactory::GetForProfile(profile_);
166 if (top_sites) {
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
172 // TopSites changes.
173 scoped_observer_.Add(top_sites.get());
177 void MostVisitedSites::GetURLThumbnail(JNIEnv* env,
178 jobject obj,
179 jstring j_url,
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(
198 const GURL& url,
199 scoped_ptr<ScopedJavaGlobalRef<jobject>> j_callback,
200 scoped_ptr<SkBitmap> bitmap) {
201 DCHECK_CURRENTLY_ON(BrowserThread::UI);
202 if (!bitmap.get()) {
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_));
206 if (top_sites)
207 top_sites->AddForcedURL(url, base::Time::Now());
208 SuggestionsService* suggestions_service =
209 (mv_source_ == SUGGESTIONS_SERVICE)
210 ? SuggestionsServiceFactory::GetForProfile(profile_)
211 : nullptr;
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,
225 const GURL& url,
226 const SkBitmap* bitmap) {
227 DCHECK_CURRENTLY_ON(BrowserThread::UI);
228 JNIEnv* env = AttachCurrentThread();
229 ScopedJavaLocalRef<jobject> j_bitmap;
230 if (bitmap) {
231 j_bitmap = gfx::ConvertToJavaBitmap(bitmap);
232 if (is_local_thumbnail) {
233 ++num_local_thumbs_;
234 } else {
235 ++num_server_thumbs_;
237 } else {
238 ++num_empty_thumbs_;
240 Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
241 env, j_callback->obj(), j_bitmap.obj());
244 void MostVisitedSites::BlacklistUrl(JNIEnv* env,
245 jobject obj,
246 jstring j_url) {
247 GURL url(ConvertJavaStringToUTF8(env, j_url));
249 switch (mv_source_) {
250 case TOP_SITES: {
251 scoped_refptr<TopSites> top_sites =
252 TopSitesFactory::GetForProfile(profile_);
253 DCHECK(top_sites);
254 top_sites->AddBlacklistedURL(url);
255 break;
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()),
265 base::Closure());
266 break;
271 void MostVisitedSites::RecordOpenedMostVisitedItem(JNIEnv* env,
272 jobject obj,
273 jint index) {
274 switch (mv_source_) {
275 case TOP_SITES: {
276 UMA_HISTOGRAM_SPARSE_SLOWLY(kOpenedItemClientHistogramName, index);
277 break;
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_);
286 } else {
287 UMA_HISTOGRAM_SPARSE_SLOWLY(kOpenedItemServerHistogramName, index);
290 break;
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();
302 // static
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()));
316 } else {
317 InitiateTopSitesQuery();
321 void MostVisitedSites::InitiateTopSitesQuery() {
322 scoped_refptr<TopSites> top_sites = TopSitesFactory::GetForProfile(profile_);
323 if (!top_sites)
324 return;
326 top_sites->GetMostVisitedURLs(
327 base::Bind(&MostVisitedSites::OnMostVisitedURLsAvailable,
328 weak_ptr_factory_.GetWeakPtr()),
329 false);
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()) {
340 num_tiles = i;
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();
363 return;
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_);
380 } else {
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,
406 num_local_thumbs_);
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);