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