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