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