• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "chrome/browser/android/most_visited_sites.h"
6 
7 #include <string>
8 #include <vector>
9 
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.h"
26 #include "chrome/browser/search/suggestions/suggestions_service_factory.h"
27 #include "chrome/browser/search/suggestions/suggestions_source.h"
28 #include "chrome/browser/thumbnails/thumbnail_list_source.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "content/public/browser/notification_source.h"
31 #include "content/public/browser/url_data_source.h"
32 #include "jni/MostVisitedSites_jni.h"
33 #include "third_party/skia/include/core/SkBitmap.h"
34 #include "ui/gfx/android/java_bitmap.h"
35 #include "ui/gfx/codec/jpeg_codec.h"
36 
37 using base::android::AttachCurrentThread;
38 using base::android::ConvertUTF8ToJavaString;
39 using base::android::ConvertJavaStringToUTF8;
40 using base::android::ScopedJavaGlobalRef;
41 using base::android::ToJavaArrayOfStrings;
42 using base::android::CheckException;
43 using content::BrowserThread;
44 using history::TopSites;
45 using suggestions::ChromeSuggestion;
46 using suggestions::SuggestionsProfile;
47 using suggestions::SuggestionsService;
48 using suggestions::SuggestionsServiceFactory;
49 
50 namespace {
51 
52 // Total number of tiles displayed.
53 const char kNumTilesHistogramName[] = "NewTabPage.NumberOfTiles";
54 // Tracking thumbnails.
55 const char kNumLocalThumbnailTilesHistogramName[] =
56     "NewTabPage.NumberOfThumbnailTiles";
57 const char kNumEmptyTilesHistogramName[] = "NewTabPage.NumberOfGrayTiles";
58 const char kNumServerTilesHistogramName[] = "NewTabPage.NumberOfExternalTiles";
59 // Client suggestion opened.
60 const char kOpenedItemClientHistogramName[] = "NewTabPage.MostVisited.client";
61 // Control group suggestion opened.
62 const char kOpenedItemControlHistogramName[] = "NewTabPage.MostVisited.client0";
63 // Server suggestion opened, no provider.
64 const char kOpenedItemServerHistogramName[] = "NewTabPage.MostVisited.server";
65 // Server suggestion opened with provider.
66 const char kOpenedItemServerProviderHistogramFormat[] =
67     "NewTabPage.MostVisited.server%d";
68 // Client impression.
69 const char kImpressionClientHistogramName[] =
70     "NewTabPage.SuggestionsImpression.client";
71 // Control group impression.
72 const char kImpressionControlHistogramName[] =
73     "NewTabPage.SuggestionsImpression.client0";
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";
80 
ExtractMostVisitedTitlesAndURLs(const history::MostVisitedURLList & visited_list,std::vector<base::string16> * titles,std::vector<std::string> * urls,int num_sites)81 void ExtractMostVisitedTitlesAndURLs(
82     const history::MostVisitedURLList& visited_list,
83     std::vector<base::string16>* titles,
84     std::vector<std::string>* urls,
85     int num_sites) {
86   size_t max = static_cast<size_t>(num_sites);
87   for (size_t i = 0; i < visited_list.size() && i < max; ++i) {
88     const history::MostVisitedURL& visited = visited_list[i];
89 
90     if (visited.url.is_empty())
91       break;  // This is the signal that there are no more real visited sites.
92 
93     titles->push_back(visited.title);
94     urls->push_back(visited.url.spec());
95   }
96 }
97 
ExtractThumbnail(const base::RefCountedMemory & image_data)98 SkBitmap ExtractThumbnail(const base::RefCountedMemory& image_data) {
99   scoped_ptr<SkBitmap> image(gfx::JPEGCodec::Decode(
100       image_data.front(),
101       image_data.size()));
102   return image.get() ? *image : SkBitmap();
103 }
104 
AddForcedURLOnUIThread(scoped_refptr<history::TopSites> top_sites,const GURL & url)105 void AddForcedURLOnUIThread(scoped_refptr<history::TopSites> top_sites,
106                             const GURL& url) {
107   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
108   top_sites->AddForcedURL(url, base::Time::Now());
109 }
110 
111 // Runs on the DB thread.
GetUrlThumbnailTask(std::string url_string,scoped_refptr<TopSites> top_sites,ScopedJavaGlobalRef<jobject> * j_callback,MostVisitedSites::LookupSuccessCallback lookup_success_ui_callback,base::Closure lookup_failed_ui_callback)112 void GetUrlThumbnailTask(
113     std::string url_string,
114     scoped_refptr<TopSites> top_sites,
115     ScopedJavaGlobalRef<jobject>* j_callback,
116     MostVisitedSites::LookupSuccessCallback lookup_success_ui_callback,
117     base::Closure lookup_failed_ui_callback) {
118   JNIEnv* env = AttachCurrentThread();
119 
120   ScopedJavaGlobalRef<jobject>* j_bitmap_ref =
121       new ScopedJavaGlobalRef<jobject>();
122 
123   GURL gurl(url_string);
124 
125   scoped_refptr<base::RefCountedMemory> data;
126   if (top_sites->GetPageThumbnail(gurl, false, &data)) {
127     SkBitmap thumbnail_bitmap = ExtractThumbnail(*data.get());
128     if (!thumbnail_bitmap.empty()) {
129       j_bitmap_ref->Reset(
130           env,
131           gfx::ConvertToJavaBitmap(&thumbnail_bitmap).obj());
132     }
133   } else {
134     // A thumbnail is not locally available for |gurl|. Make sure it is put in
135     // the list to be fetched at the next visit to this site.
136     BrowserThread::PostTask(
137         BrowserThread::UI, FROM_HERE,
138         base::Bind(AddForcedURLOnUIThread, top_sites, gurl));
139 
140     // If appropriate, return on the UI thread to execute the proper callback.
141     if (!lookup_failed_ui_callback.is_null()) {
142       BrowserThread::PostTask(
143           BrowserThread::UI, FROM_HERE, lookup_failed_ui_callback);
144       delete j_bitmap_ref;
145       return;
146     }
147   }
148 
149   // Since j_callback is owned by this callback, when the callback falls out of
150   // scope it will be deleted. We need to pass ownership to the next callback.
151   ScopedJavaGlobalRef<jobject>* j_callback_pass =
152       new ScopedJavaGlobalRef<jobject>(*j_callback);
153   BrowserThread::PostTask(
154       BrowserThread::UI, FROM_HERE,
155       base::Bind(lookup_success_ui_callback, base::Owned(j_bitmap_ref),
156                  base::Owned(j_callback_pass)));
157 }
158 
159 // Log an event for a given |histogram| at a given element |position|. This
160 // routine exists because regular histogram macros are cached thus can't be used
161 // if the name of the histogram will change at a given call site.
LogHistogramEvent(const std::string & histogram,int position,int num_sites)162 void LogHistogramEvent(const std::string& histogram, int position,
163                        int num_sites) {
164   base::HistogramBase* counter = base::LinearHistogram::FactoryGet(
165       histogram,
166       1,
167       num_sites,
168       num_sites + 1,
169       base::Histogram::kUmaTargetedHistogramFlag);
170   if (counter)
171     counter->Add(position);
172 }
173 
174 }  // namespace
175 
MostVisitedSites(Profile * profile)176 MostVisitedSites::MostVisitedSites(Profile* profile)
177     : profile_(profile), num_sites_(0), is_control_group_(false),
178       num_local_thumbs_(0), num_server_thumbs_(0), num_empty_thumbs_(0),
179       weak_ptr_factory_(this) {
180   // Register the debugging page for the Suggestions Service and the thumbnails
181   // debugging page.
182   content::URLDataSource::Add(profile_,
183                               new suggestions::SuggestionsSource(profile_));
184   content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_));
185 }
186 
~MostVisitedSites()187 MostVisitedSites::~MostVisitedSites() {
188 }
189 
Destroy(JNIEnv * env,jobject obj)190 void MostVisitedSites::Destroy(JNIEnv* env, jobject obj) {
191   delete this;
192 }
193 
OnLoadingComplete(JNIEnv * env,jobject obj)194 void MostVisitedSites::OnLoadingComplete(JNIEnv* env, jobject obj) {
195   RecordUMAMetrics();
196 }
197 
SetMostVisitedURLsObserver(JNIEnv * env,jobject obj,jobject j_observer,jint num_sites)198 void MostVisitedSites::SetMostVisitedURLsObserver(JNIEnv* env,
199                                                   jobject obj,
200                                                   jobject j_observer,
201                                                   jint num_sites) {
202   observer_.Reset(env, j_observer);
203   num_sites_ = num_sites;
204 
205   QueryMostVisitedURLs();
206 
207   history::TopSites* top_sites = profile_->GetTopSites();
208   if (top_sites) {
209     // TopSites updates itself after a delay. To ensure up-to-date results,
210     // force an update now.
211     top_sites->SyncWithHistory();
212 
213     // Register for notification when TopSites changes so that we can update
214     // ourself.
215     registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED,
216                    content::Source<history::TopSites>(top_sites));
217   }
218 }
219 
220 // Called from the UI Thread.
GetURLThumbnail(JNIEnv * env,jobject obj,jstring url,jobject j_callback_obj)221 void MostVisitedSites::GetURLThumbnail(JNIEnv* env,
222                                        jobject obj,
223                                        jstring url,
224                                        jobject j_callback_obj) {
225   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
226   ScopedJavaGlobalRef<jobject>* j_callback =
227       new ScopedJavaGlobalRef<jobject>();
228   j_callback->Reset(env, j_callback_obj);
229 
230   std::string url_string = ConvertJavaStringToUTF8(env, url);
231   scoped_refptr<TopSites> top_sites(profile_->GetTopSites());
232 
233   // If the Suggestions service is enabled, create a callback to fetch a
234   // server thumbnail from it, in case the local thumbnail is not found.
235   SuggestionsService* suggestions_service =
236       SuggestionsServiceFactory::GetForProfile(profile_);
237   base::Closure lookup_failed_callback = suggestions_service ?
238       base::Bind(&MostVisitedSites::GetSuggestionsThumbnailOnUIThread,
239                  weak_ptr_factory_.GetWeakPtr(),
240                  suggestions_service, url_string,
241                  base::Owned(new ScopedJavaGlobalRef<jobject>(*j_callback))) :
242       base::Closure();
243   LookupSuccessCallback lookup_success_callback =
244       base::Bind(&MostVisitedSites::OnObtainedThumbnail,
245                  weak_ptr_factory_.GetWeakPtr());
246 
247   BrowserThread::PostTask(
248       BrowserThread::DB, FROM_HERE,
249           base::Bind(
250               &GetUrlThumbnailTask, url_string, top_sites,
251               base::Owned(j_callback), lookup_success_callback,
252               lookup_failed_callback));
253 }
254 
BlacklistUrl(JNIEnv * env,jobject obj,jstring j_url)255 void MostVisitedSites::BlacklistUrl(JNIEnv* env,
256                                     jobject obj,
257                                     jstring j_url) {
258   std::string url = ConvertJavaStringToUTF8(env, j_url);
259 
260   switch (mv_source_) {
261     case TOP_SITES: {
262       TopSites* top_sites = profile_->GetTopSites();
263       DCHECK(top_sites);
264       top_sites->AddBlacklistedURL(GURL(url));
265       break;
266     }
267 
268     case SUGGESTIONS_SERVICE: {
269       SuggestionsService* suggestions_service =
270           SuggestionsServiceFactory::GetForProfile(profile_);
271       DCHECK(suggestions_service);
272       suggestions_service->BlacklistURL(
273           GURL(url),
274           base::Bind(
275               &MostVisitedSites::OnSuggestionsProfileAvailable,
276               weak_ptr_factory_.GetWeakPtr(),
277               base::Owned(new ScopedJavaGlobalRef<jobject>(observer_))));
278       break;
279     }
280   }
281 }
282 
RecordOpenedMostVisitedItem(JNIEnv * env,jobject obj,jint index)283 void MostVisitedSites::RecordOpenedMostVisitedItem(JNIEnv* env,
284                                                    jobject obj,
285                                                    jint index) {
286   switch (mv_source_) {
287     case TOP_SITES: {
288       const std::string histogram = is_control_group_ ?
289           kOpenedItemControlHistogramName : kOpenedItemClientHistogramName;
290       LogHistogramEvent(histogram, index, num_sites_);
291       break;
292     }
293     case SUGGESTIONS_SERVICE: {
294       if (server_suggestions_.suggestions_size() > index) {
295         if (server_suggestions_.suggestions(index).providers_size()) {
296           std::string histogram = base::StringPrintf(
297               kOpenedItemServerProviderHistogramFormat,
298               server_suggestions_.suggestions(index).providers(0));
299           LogHistogramEvent(histogram, index, num_sites_);
300         } else {
301           UMA_HISTOGRAM_SPARSE_SLOWLY(kOpenedItemServerHistogramName, index);
302         }
303       }
304       break;
305     }
306   }
307 }
308 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)309 void MostVisitedSites::Observe(int type,
310                                const content::NotificationSource& source,
311                                const content::NotificationDetails& details) {
312   DCHECK_EQ(type, chrome::NOTIFICATION_TOP_SITES_CHANGED);
313 
314   if (mv_source_ == TOP_SITES) {
315     // The displayed suggestions are invalidated.
316     QueryMostVisitedURLs();
317   }
318 }
319 
320 // static
Register(JNIEnv * env)321 bool MostVisitedSites::Register(JNIEnv* env) {
322   return RegisterNativesImpl(env);
323 }
324 
QueryMostVisitedURLs()325 void MostVisitedSites::QueryMostVisitedURLs() {
326   SuggestionsService* suggestions_service =
327       SuggestionsServiceFactory::GetForProfile(profile_);
328   if (suggestions_service) {
329     // Suggestions service is enabled, initiate a query.
330     suggestions_service->FetchSuggestionsData(
331         base::Bind(
332           &MostVisitedSites::OnSuggestionsProfileAvailable,
333           weak_ptr_factory_.GetWeakPtr(),
334           base::Owned(new ScopedJavaGlobalRef<jobject>(observer_))));
335   } else {
336     InitiateTopSitesQuery();
337   }
338 }
339 
InitiateTopSitesQuery()340 void MostVisitedSites::InitiateTopSitesQuery() {
341   TopSites* top_sites = profile_->GetTopSites();
342   if (!top_sites)
343     return;
344 
345   top_sites->GetMostVisitedURLs(
346       base::Bind(
347           &MostVisitedSites::OnMostVisitedURLsAvailable,
348           weak_ptr_factory_.GetWeakPtr(),
349           base::Owned(new ScopedJavaGlobalRef<jobject>(observer_)),
350           num_sites_),
351       false);
352 }
353 
OnMostVisitedURLsAvailable(ScopedJavaGlobalRef<jobject> * j_observer,int num_sites,const history::MostVisitedURLList & visited_list)354 void MostVisitedSites::OnMostVisitedURLsAvailable(
355     ScopedJavaGlobalRef<jobject>* j_observer,
356     int num_sites,
357     const history::MostVisitedURLList& visited_list) {
358   std::vector<base::string16> titles;
359   std::vector<std::string> urls;
360   ExtractMostVisitedTitlesAndURLs(visited_list, &titles, &urls, num_sites);
361 
362   mv_source_ = TOP_SITES;
363 
364   int num_tiles = urls.size();
365   UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName, num_tiles);
366   const std::string histogram = is_control_group_ ?
367       kImpressionControlHistogramName : kImpressionClientHistogramName;
368   for (int i = 0; i < num_tiles; ++i) {
369     LogHistogramEvent(histogram, i, num_sites_);
370   }
371 
372   JNIEnv* env = AttachCurrentThread();
373   Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
374       env,
375       j_observer->obj(),
376       ToJavaArrayOfStrings(env, titles).obj(),
377       ToJavaArrayOfStrings(env, urls).obj());
378 }
379 
OnSuggestionsProfileAvailable(ScopedJavaGlobalRef<jobject> * j_observer,const SuggestionsProfile & suggestions_profile)380 void MostVisitedSites::OnSuggestionsProfileAvailable(
381     ScopedJavaGlobalRef<jobject>* j_observer,
382     const SuggestionsProfile& suggestions_profile) {
383   int size = suggestions_profile.suggestions_size();
384 
385   // Determine if the user is in a control group (they would have received
386   // suggestions, but are in a group where they shouldn't).
387   is_control_group_ = size && SuggestionsService::IsControlGroup();
388 
389   // If no suggestions data is available or the user is in a control group,
390   // initiate Top Sites query.
391   if (is_control_group_ || !size) {
392     InitiateTopSitesQuery();
393     return;
394   }
395 
396   std::vector<base::string16> titles;
397   std::vector<std::string> urls;
398 
399   int i = 0;
400   for (; i < size && i < num_sites_; ++i) {
401     const ChromeSuggestion& suggestion = suggestions_profile.suggestions(i);
402     titles.push_back(base::UTF8ToUTF16(suggestion.title()));
403     urls.push_back(suggestion.url());
404     if (suggestion.providers_size()) {
405       std::string histogram = base::StringPrintf(
406           kImpressionServerHistogramFormat, suggestion.providers(0));
407       LogHistogramEvent(histogram, i, num_sites_);
408     } else {
409       UMA_HISTOGRAM_SPARSE_SLOWLY(kImpressionServerHistogramName, i);
410     }
411   }
412   UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName, i);
413 
414   mv_source_ = SUGGESTIONS_SERVICE;
415   // Keep a copy of the suggestions for eventual logging.
416   server_suggestions_ = suggestions_profile;
417 
418   JNIEnv* env = AttachCurrentThread();
419   Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
420       env,
421       j_observer->obj(),
422       ToJavaArrayOfStrings(env, titles).obj(),
423       ToJavaArrayOfStrings(env, urls).obj());
424 }
425 
OnObtainedThumbnail(ScopedJavaGlobalRef<jobject> * bitmap,ScopedJavaGlobalRef<jobject> * j_callback)426 void MostVisitedSites::OnObtainedThumbnail(
427     ScopedJavaGlobalRef<jobject>* bitmap,
428     ScopedJavaGlobalRef<jobject>* j_callback) {
429   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
430   JNIEnv* env = AttachCurrentThread();
431   if (bitmap->obj()) {
432     num_local_thumbs_++;
433   } else {
434     num_empty_thumbs_++;
435   }
436   Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
437       env, j_callback->obj(), bitmap->obj());
438 }
439 
GetSuggestionsThumbnailOnUIThread(SuggestionsService * suggestions_service,const std::string & url_string,ScopedJavaGlobalRef<jobject> * j_callback)440 void MostVisitedSites::GetSuggestionsThumbnailOnUIThread(
441     SuggestionsService* suggestions_service,
442     const std::string& url_string,
443     ScopedJavaGlobalRef<jobject>* j_callback) {
444   suggestions_service->GetPageThumbnail(
445       GURL(url_string),
446       base::Bind(&MostVisitedSites::OnSuggestionsThumbnailAvailable,
447                  weak_ptr_factory_.GetWeakPtr(),
448                  base::Owned(new ScopedJavaGlobalRef<jobject>(*j_callback))));
449 }
450 
OnSuggestionsThumbnailAvailable(ScopedJavaGlobalRef<jobject> * j_callback,const GURL & url,const SkBitmap * bitmap)451 void MostVisitedSites::OnSuggestionsThumbnailAvailable(
452     ScopedJavaGlobalRef<jobject>* j_callback,
453     const GURL& url,
454     const SkBitmap* bitmap) {
455   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
456   JNIEnv* env = AttachCurrentThread();
457 
458   ScopedJavaGlobalRef<jobject>* j_bitmap_ref =
459       new ScopedJavaGlobalRef<jobject>();
460   if (bitmap) {
461     num_server_thumbs_++;
462     j_bitmap_ref->Reset(
463         env,
464         gfx::ConvertToJavaBitmap(bitmap).obj());
465   } else {
466     num_empty_thumbs_++;
467   }
468 
469   Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
470       env, j_callback->obj(), j_bitmap_ref->obj());
471 }
472 
RecordUMAMetrics()473 void MostVisitedSites::RecordUMAMetrics() {
474   UMA_HISTOGRAM_SPARSE_SLOWLY(kNumLocalThumbnailTilesHistogramName,
475                               num_local_thumbs_);
476   num_local_thumbs_ = 0;
477   UMA_HISTOGRAM_SPARSE_SLOWLY(kNumEmptyTilesHistogramName, num_empty_thumbs_);
478   num_empty_thumbs_ = 0;
479   UMA_HISTOGRAM_SPARSE_SLOWLY(kNumServerTilesHistogramName, num_server_thumbs_);
480   num_server_thumbs_ = 0;
481 }
482 
Init(JNIEnv * env,jobject obj,jobject jprofile)483 static jlong Init(JNIEnv* env, jobject obj, jobject jprofile) {
484   MostVisitedSites* most_visited_sites =
485       new MostVisitedSites(ProfileAndroid::FromProfileAndroid(jprofile));
486   return reinterpret_cast<intptr_t>(most_visited_sites);
487 }
488