most_visited_sites.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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/strings/utf_string_conversions.h" 16#include "base/time/time.h" 17#include "chrome/browser/chrome_notification_types.h" 18#include "chrome/browser/history/top_sites.h" 19#include "chrome/browser/profiles/profile.h" 20#include "chrome/browser/profiles/profile_android.h" 21#include "chrome/browser/search/suggestions/proto/suggestions.pb.h" 22#include "chrome/browser/search/suggestions/suggestions_service.h" 23#include "chrome/browser/search/suggestions/suggestions_service_factory.h" 24#include "chrome/browser/search/suggestions/suggestions_source.h" 25#include "chrome/browser/thumbnails/thumbnail_list_source.h" 26#include "content/public/browser/browser_thread.h" 27#include "content/public/browser/notification_source.h" 28#include "content/public/browser/url_data_source.h" 29#include "jni/MostVisitedSites_jni.h" 30#include "third_party/skia/include/core/SkBitmap.h" 31#include "ui/gfx/android/java_bitmap.h" 32#include "ui/gfx/codec/jpeg_codec.h" 33 34using base::android::AttachCurrentThread; 35using base::android::ConvertUTF8ToJavaString; 36using base::android::ConvertJavaStringToUTF8; 37using base::android::ScopedJavaGlobalRef; 38using base::android::ToJavaArrayOfStrings; 39using base::android::CheckException; 40using content::BrowserThread; 41using history::TopSites; 42using suggestions::ChromeSuggestion; 43using suggestions::SuggestionsProfile; 44using suggestions::SuggestionsService; 45using suggestions::SuggestionsServiceFactory; 46 47namespace { 48 49void ExtractMostVisitedTitlesAndURLs( 50 const history::MostVisitedURLList& visited_list, 51 std::vector<base::string16>* titles, 52 std::vector<std::string>* urls, 53 int num_sites) { 54 size_t max = static_cast<size_t>(num_sites); 55 for (size_t i = 0; i < visited_list.size() && i < max; ++i) { 56 const history::MostVisitedURL& visited = visited_list[i]; 57 58 if (visited.url.is_empty()) 59 break; // This is the signal that there are no more real visited sites. 60 61 titles->push_back(visited.title); 62 urls->push_back(visited.url.spec()); 63 } 64} 65 66SkBitmap ExtractThumbnail(const base::RefCountedMemory& image_data) { 67 scoped_ptr<SkBitmap> image(gfx::JPEGCodec::Decode( 68 image_data.front(), 69 image_data.size())); 70 return image.get() ? *image : SkBitmap(); 71} 72 73void OnObtainedThumbnail( 74 ScopedJavaGlobalRef<jobject>* bitmap, 75 ScopedJavaGlobalRef<jobject>* j_callback) { 76 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 77 JNIEnv* env = AttachCurrentThread(); 78 Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable( 79 env, j_callback->obj(), bitmap->obj()); 80} 81 82void AddForcedURLOnUIThread(scoped_refptr<history::TopSites> top_sites, 83 const GURL& url) { 84 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 85 top_sites->AddForcedURL(url, base::Time::Now()); 86} 87 88void OnSuggestionsThumbnailAvailable( 89 ScopedJavaGlobalRef<jobject>* j_callback, 90 const GURL& url, 91 const SkBitmap* bitmap) { 92 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 93 JNIEnv* env = AttachCurrentThread(); 94 95 ScopedJavaGlobalRef<jobject>* j_bitmap_ref = 96 new ScopedJavaGlobalRef<jobject>(); 97 if (bitmap) { 98 j_bitmap_ref->Reset( 99 env, 100 gfx::ConvertToJavaBitmap(bitmap).obj()); 101 } 102 103 Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable( 104 env, j_callback->obj(), j_bitmap_ref->obj()); 105} 106 107// Runs on the DB thread. 108void GetUrlThumbnailTask( 109 std::string url_string, 110 scoped_refptr<TopSites> top_sites, 111 ScopedJavaGlobalRef<jobject>* j_callback, 112 base::Closure lookup_failed_ui_callback) { 113 JNIEnv* env = AttachCurrentThread(); 114 115 ScopedJavaGlobalRef<jobject>* j_bitmap_ref = 116 new ScopedJavaGlobalRef<jobject>(); 117 118 GURL gurl(url_string); 119 120 scoped_refptr<base::RefCountedMemory> data; 121 if (top_sites->GetPageThumbnail(gurl, false, &data)) { 122 SkBitmap thumbnail_bitmap = ExtractThumbnail(*data.get()); 123 if (!thumbnail_bitmap.empty()) { 124 j_bitmap_ref->Reset( 125 env, 126 gfx::ConvertToJavaBitmap(&thumbnail_bitmap).obj()); 127 } 128 } else { 129 // A thumbnail is not locally available for |gurl|. Make sure it is put in 130 // the list to be fetched at the next visit to this site. 131 BrowserThread::PostTask( 132 BrowserThread::UI, FROM_HERE, 133 base::Bind(AddForcedURLOnUIThread, top_sites, gurl)); 134 135 // If appropriate, return on the UI thread to execute the proper callback. 136 if (!lookup_failed_ui_callback.is_null()) { 137 BrowserThread::PostTask( 138 BrowserThread::UI, FROM_HERE, lookup_failed_ui_callback); 139 return; 140 } 141 } 142 143 // Since j_callback is owned by this callback, when the callback falls out of 144 // scope it will be deleted. We need to pass ownership to the next callback. 145 ScopedJavaGlobalRef<jobject>* j_callback_pass = 146 new ScopedJavaGlobalRef<jobject>(*j_callback); 147 BrowserThread::PostTask( 148 BrowserThread::UI, FROM_HERE, 149 base::Bind( 150 &OnObtainedThumbnail, 151 base::Owned(j_bitmap_ref), base::Owned(j_callback_pass))); 152} 153 154void GetSuggestionsThumbnailOnUIThread( 155 SuggestionsService* suggestions_service, 156 const std::string& url_string, 157 ScopedJavaGlobalRef<jobject>* j_callback) { 158 suggestions_service->GetPageThumbnail( 159 GURL(url_string), 160 base::Bind(&OnSuggestionsThumbnailAvailable, 161 base::Owned(new ScopedJavaGlobalRef<jobject>(*j_callback)))); 162} 163 164} // namespace 165 166MostVisitedSites::MostVisitedSites(Profile* profile) 167 : profile_(profile), num_sites_(0), weak_ptr_factory_(this) { 168 // Register the debugging page for the Suggestions Service and the thumbnails 169 // debugging page. 170 content::URLDataSource::Add(profile_, 171 new suggestions::SuggestionsSource(profile_)); 172 content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_)); 173} 174 175MostVisitedSites::~MostVisitedSites() { 176} 177 178void MostVisitedSites::Destroy(JNIEnv* env, jobject obj) { 179 delete this; 180} 181 182void MostVisitedSites::SetMostVisitedURLsObserver(JNIEnv* env, 183 jobject obj, 184 jobject j_observer, 185 jint num_sites) { 186 observer_.Reset(env, j_observer); 187 num_sites_ = num_sites; 188 189 QueryMostVisitedURLs(); 190 191 history::TopSites* top_sites = profile_->GetTopSites(); 192 if (top_sites) { 193 // TopSites updates itself after a delay. To ensure up-to-date results, 194 // force an update now. 195 top_sites->SyncWithHistory(); 196 197 // Register for notification when TopSites changes so that we can update 198 // ourself. 199 registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED, 200 content::Source<history::TopSites>(top_sites)); 201 } 202} 203 204// Called from the UI Thread. 205void MostVisitedSites::GetURLThumbnail(JNIEnv* env, 206 jobject obj, 207 jstring url, 208 jobject j_callback_obj) { 209 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 210 ScopedJavaGlobalRef<jobject>* j_callback = 211 new ScopedJavaGlobalRef<jobject>(); 212 j_callback->Reset(env, j_callback_obj); 213 214 std::string url_string = ConvertJavaStringToUTF8(env, url); 215 scoped_refptr<TopSites> top_sites(profile_->GetTopSites()); 216 217 // If the Suggestions service is enabled, create a callback to fetch a 218 // server thumbnail from it, in case the local thumbnail is not found. 219 SuggestionsService* suggestions_service = 220 SuggestionsServiceFactory::GetForProfile(profile_); 221 base::Closure lookup_failed_callback = suggestions_service ? 222 base::Bind(&GetSuggestionsThumbnailOnUIThread, 223 suggestions_service, url_string, 224 base::Owned(new ScopedJavaGlobalRef<jobject>(*j_callback))) : 225 base::Closure(); 226 BrowserThread::PostTask( 227 BrowserThread::DB, FROM_HERE, 228 base::Bind( 229 &GetUrlThumbnailTask, url_string, top_sites, 230 base::Owned(j_callback), lookup_failed_callback)); 231} 232 233void MostVisitedSites::BlacklistUrl(JNIEnv* env, 234 jobject obj, 235 jstring j_url) { 236 std::string url = ConvertJavaStringToUTF8(env, j_url); 237 238 switch (mv_source_) { 239 case TOP_SITES: { 240 TopSites* top_sites = profile_->GetTopSites(); 241 DCHECK(top_sites); 242 top_sites->AddBlacklistedURL(GURL(url)); 243 break; 244 } 245 246 case SUGGESTIONS_SERVICE: { 247 SuggestionsService* suggestions_service = 248 SuggestionsServiceFactory::GetForProfile(profile_); 249 DCHECK(suggestions_service); 250 suggestions_service->BlacklistURL( 251 GURL(url), 252 base::Bind( 253 &MostVisitedSites::OnSuggestionsProfileAvailable, 254 weak_ptr_factory_.GetWeakPtr(), 255 base::Owned(new ScopedJavaGlobalRef<jobject>(observer_)))); 256 break; 257 } 258 } 259} 260 261void MostVisitedSites::Observe(int type, 262 const content::NotificationSource& source, 263 const content::NotificationDetails& details) { 264 DCHECK_EQ(type, chrome::NOTIFICATION_TOP_SITES_CHANGED); 265 266 if (mv_source_ == TOP_SITES) { 267 // The displayed suggestions are invalidated. 268 QueryMostVisitedURLs(); 269 } 270} 271 272// static 273bool MostVisitedSites::Register(JNIEnv* env) { 274 return RegisterNativesImpl(env); 275} 276 277void MostVisitedSites::QueryMostVisitedURLs() { 278 SuggestionsService* suggestions_service = 279 SuggestionsServiceFactory::GetForProfile(profile_); 280 if (suggestions_service) { 281 // Suggestions service is enabled, initiate a query. 282 suggestions_service->FetchSuggestionsData( 283 base::Bind( 284 &MostVisitedSites::OnSuggestionsProfileAvailable, 285 weak_ptr_factory_.GetWeakPtr(), 286 base::Owned(new ScopedJavaGlobalRef<jobject>(observer_)))); 287 } else { 288 InitiateTopSitesQuery(); 289 } 290} 291 292void MostVisitedSites::InitiateTopSitesQuery() { 293 TopSites* top_sites = profile_->GetTopSites(); 294 if (!top_sites) 295 return; 296 297 top_sites->GetMostVisitedURLs( 298 base::Bind( 299 &MostVisitedSites::OnMostVisitedURLsAvailable, 300 weak_ptr_factory_.GetWeakPtr(), 301 base::Owned(new ScopedJavaGlobalRef<jobject>(observer_)), 302 num_sites_), 303 false); 304} 305 306void MostVisitedSites::OnMostVisitedURLsAvailable( 307 ScopedJavaGlobalRef<jobject>* j_observer, 308 int num_sites, 309 const history::MostVisitedURLList& visited_list) { 310 std::vector<base::string16> titles; 311 std::vector<std::string> urls; 312 ExtractMostVisitedTitlesAndURLs(visited_list, &titles, &urls, num_sites); 313 314 mv_source_ = TOP_SITES; 315 316 JNIEnv* env = AttachCurrentThread(); 317 Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable( 318 env, 319 j_observer->obj(), 320 ToJavaArrayOfStrings(env, titles).obj(), 321 ToJavaArrayOfStrings(env, urls).obj()); 322} 323 324void MostVisitedSites::OnSuggestionsProfileAvailable( 325 ScopedJavaGlobalRef<jobject>* j_observer, 326 const SuggestionsProfile& suggestions_profile) { 327 size_t size = suggestions_profile.suggestions_size(); 328 if (size == 0) { 329 // No suggestions data available, initiate Top Sites query. 330 InitiateTopSitesQuery(); 331 return; 332 } 333 334 std::vector<base::string16> titles; 335 std::vector<std::string> urls; 336 for (size_t i = 0; i < size; ++i) { 337 const ChromeSuggestion& suggestion = suggestions_profile.suggestions(i); 338 titles.push_back(base::UTF8ToUTF16(suggestion.title())); 339 urls.push_back(suggestion.url()); 340 } 341 342 mv_source_ = SUGGESTIONS_SERVICE; 343 344 JNIEnv* env = AttachCurrentThread(); 345 Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable( 346 env, 347 j_observer->obj(), 348 ToJavaArrayOfStrings(env, titles).obj(), 349 ToJavaArrayOfStrings(env, urls).obj()); 350} 351 352static jlong Init(JNIEnv* env, jobject obj, jobject jprofile) { 353 MostVisitedSites* most_visited_sites = 354 new MostVisitedSites(ProfileAndroid::FromProfileAndroid(jprofile)); 355 return reinterpret_cast<intptr_t>(most_visited_sites); 356} 357