Home | History | Annotate | Download | only in android
      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 
     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 
     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 
    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.
    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.
    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 
    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 
    187 MostVisitedSites::~MostVisitedSites() {
    188 }
    189 
    190 void MostVisitedSites::Destroy(JNIEnv* env, jobject obj) {
    191   delete this;
    192 }
    193 
    194 void MostVisitedSites::OnLoadingComplete(JNIEnv* env, jobject obj) {
    195   RecordUMAMetrics();
    196 }
    197 
    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.
    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 
    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 
    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 
    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
    321 bool MostVisitedSites::Register(JNIEnv* env) {
    322   return RegisterNativesImpl(env);
    323 }
    324 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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