// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/android/most_visited_sites.h" #include #include #include "base/android/jni_android.h" #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/android/scoped_java_ref.h" #include "base/callback.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/history/top_sites.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_android.h" #include "chrome/browser/search/suggestions/proto/suggestions.pb.h" #include "chrome/browser/search/suggestions/suggestions_service.h" #include "chrome/browser/search/suggestions/suggestions_service_factory.h" #include "chrome/browser/search/suggestions/suggestions_source.h" #include "chrome/browser/thumbnails/thumbnail_list_source.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/url_data_source.h" #include "jni/MostVisitedSites_jni.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/gfx/android/java_bitmap.h" #include "ui/gfx/codec/jpeg_codec.h" using base::android::AttachCurrentThread; using base::android::ConvertUTF8ToJavaString; using base::android::ConvertJavaStringToUTF8; using base::android::ScopedJavaGlobalRef; using base::android::ToJavaArrayOfStrings; using base::android::CheckException; using content::BrowserThread; using history::TopSites; using suggestions::ChromeSuggestion; using suggestions::SuggestionsProfile; using suggestions::SuggestionsService; using suggestions::SuggestionsServiceFactory; namespace { void ExtractMostVisitedTitlesAndURLs( const history::MostVisitedURLList& visited_list, std::vector* titles, std::vector* urls, int num_sites) { size_t max = static_cast(num_sites); for (size_t i = 0; i < visited_list.size() && i < max; ++i) { const history::MostVisitedURL& visited = visited_list[i]; if (visited.url.is_empty()) break; // This is the signal that there are no more real visited sites. titles->push_back(visited.title); urls->push_back(visited.url.spec()); } } SkBitmap ExtractThumbnail(const base::RefCountedMemory& image_data) { scoped_ptr image(gfx::JPEGCodec::Decode( image_data.front(), image_data.size())); return image.get() ? *image : SkBitmap(); } void OnObtainedThumbnail( ScopedJavaGlobalRef* bitmap, ScopedJavaGlobalRef* j_callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); JNIEnv* env = AttachCurrentThread(); Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable( env, j_callback->obj(), bitmap->obj()); } void AddForcedURLOnUIThread(scoped_refptr top_sites, const GURL& url) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); top_sites->AddForcedURL(url, base::Time::Now()); } void OnSuggestionsThumbnailAvailable( ScopedJavaGlobalRef* j_callback, const GURL& url, const SkBitmap* bitmap) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); JNIEnv* env = AttachCurrentThread(); ScopedJavaGlobalRef* j_bitmap_ref = new ScopedJavaGlobalRef(); if (bitmap) { j_bitmap_ref->Reset( env, gfx::ConvertToJavaBitmap(bitmap).obj()); } Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable( env, j_callback->obj(), j_bitmap_ref->obj()); } // Runs on the DB thread. void GetUrlThumbnailTask( std::string url_string, scoped_refptr top_sites, ScopedJavaGlobalRef* j_callback, base::Closure lookup_failed_ui_callback) { JNIEnv* env = AttachCurrentThread(); ScopedJavaGlobalRef* j_bitmap_ref = new ScopedJavaGlobalRef(); GURL gurl(url_string); scoped_refptr data; if (top_sites->GetPageThumbnail(gurl, false, &data)) { SkBitmap thumbnail_bitmap = ExtractThumbnail(*data.get()); if (!thumbnail_bitmap.empty()) { j_bitmap_ref->Reset( env, gfx::ConvertToJavaBitmap(&thumbnail_bitmap).obj()); } } else { // A thumbnail is not locally available for |gurl|. Make sure it is put in // the list to be fetched at the next visit to this site. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(AddForcedURLOnUIThread, top_sites, gurl)); // If appropriate, return on the UI thread to execute the proper callback. if (!lookup_failed_ui_callback.is_null()) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, lookup_failed_ui_callback); return; } } // Since j_callback is owned by this callback, when the callback falls out of // scope it will be deleted. We need to pass ownership to the next callback. ScopedJavaGlobalRef* j_callback_pass = new ScopedJavaGlobalRef(*j_callback); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind( &OnObtainedThumbnail, base::Owned(j_bitmap_ref), base::Owned(j_callback_pass))); } void GetSuggestionsThumbnailOnUIThread( SuggestionsService* suggestions_service, const std::string& url_string, ScopedJavaGlobalRef* j_callback) { suggestions_service->GetPageThumbnail( GURL(url_string), base::Bind(&OnSuggestionsThumbnailAvailable, base::Owned(new ScopedJavaGlobalRef(*j_callback)))); } } // namespace MostVisitedSites::MostVisitedSites(Profile* profile) : profile_(profile), num_sites_(0), weak_ptr_factory_(this) { // Register the debugging page for the Suggestions Service and the thumbnails // debugging page. content::URLDataSource::Add(profile_, new suggestions::SuggestionsSource(profile_)); content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_)); } MostVisitedSites::~MostVisitedSites() { } void MostVisitedSites::Destroy(JNIEnv* env, jobject obj) { delete this; } void MostVisitedSites::SetMostVisitedURLsObserver(JNIEnv* env, jobject obj, jobject j_observer, jint num_sites) { observer_.Reset(env, j_observer); num_sites_ = num_sites; QueryMostVisitedURLs(); history::TopSites* top_sites = profile_->GetTopSites(); if (top_sites) { // TopSites updates itself after a delay. To ensure up-to-date results, // force an update now. top_sites->SyncWithHistory(); // Register for notification when TopSites changes so that we can update // ourself. registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED, content::Source(top_sites)); } } // Called from the UI Thread. void MostVisitedSites::GetURLThumbnail(JNIEnv* env, jobject obj, jstring url, jobject j_callback_obj) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); ScopedJavaGlobalRef* j_callback = new ScopedJavaGlobalRef(); j_callback->Reset(env, j_callback_obj); std::string url_string = ConvertJavaStringToUTF8(env, url); scoped_refptr top_sites(profile_->GetTopSites()); // If the Suggestions service is enabled, create a callback to fetch a // server thumbnail from it, in case the local thumbnail is not found. SuggestionsService* suggestions_service = SuggestionsServiceFactory::GetForProfile(profile_); base::Closure lookup_failed_callback = suggestions_service ? base::Bind(&GetSuggestionsThumbnailOnUIThread, suggestions_service, url_string, base::Owned(new ScopedJavaGlobalRef(*j_callback))) : base::Closure(); BrowserThread::PostTask( BrowserThread::DB, FROM_HERE, base::Bind( &GetUrlThumbnailTask, url_string, top_sites, base::Owned(j_callback), lookup_failed_callback)); } void MostVisitedSites::BlacklistUrl(JNIEnv* env, jobject obj, jstring j_url) { std::string url = ConvertJavaStringToUTF8(env, j_url); switch (mv_source_) { case TOP_SITES: { TopSites* top_sites = profile_->GetTopSites(); DCHECK(top_sites); top_sites->AddBlacklistedURL(GURL(url)); break; } case SUGGESTIONS_SERVICE: { SuggestionsService* suggestions_service = SuggestionsServiceFactory::GetForProfile(profile_); DCHECK(suggestions_service); suggestions_service->BlacklistURL( GURL(url), base::Bind( &MostVisitedSites::OnSuggestionsProfileAvailable, weak_ptr_factory_.GetWeakPtr(), base::Owned(new ScopedJavaGlobalRef(observer_)))); break; } } } void MostVisitedSites::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK_EQ(type, chrome::NOTIFICATION_TOP_SITES_CHANGED); if (mv_source_ == TOP_SITES) { // The displayed suggestions are invalidated. QueryMostVisitedURLs(); } } // static bool MostVisitedSites::Register(JNIEnv* env) { return RegisterNativesImpl(env); } void MostVisitedSites::QueryMostVisitedURLs() { SuggestionsService* suggestions_service = SuggestionsServiceFactory::GetForProfile(profile_); if (suggestions_service) { // Suggestions service is enabled, initiate a query. suggestions_service->FetchSuggestionsData( base::Bind( &MostVisitedSites::OnSuggestionsProfileAvailable, weak_ptr_factory_.GetWeakPtr(), base::Owned(new ScopedJavaGlobalRef(observer_)))); } else { InitiateTopSitesQuery(); } } void MostVisitedSites::InitiateTopSitesQuery() { TopSites* top_sites = profile_->GetTopSites(); if (!top_sites) return; top_sites->GetMostVisitedURLs( base::Bind( &MostVisitedSites::OnMostVisitedURLsAvailable, weak_ptr_factory_.GetWeakPtr(), base::Owned(new ScopedJavaGlobalRef(observer_)), num_sites_), false); } void MostVisitedSites::OnMostVisitedURLsAvailable( ScopedJavaGlobalRef* j_observer, int num_sites, const history::MostVisitedURLList& visited_list) { std::vector titles; std::vector urls; ExtractMostVisitedTitlesAndURLs(visited_list, &titles, &urls, num_sites); mv_source_ = TOP_SITES; JNIEnv* env = AttachCurrentThread(); Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable( env, j_observer->obj(), ToJavaArrayOfStrings(env, titles).obj(), ToJavaArrayOfStrings(env, urls).obj()); } void MostVisitedSites::OnSuggestionsProfileAvailable( ScopedJavaGlobalRef* j_observer, const SuggestionsProfile& suggestions_profile) { size_t size = suggestions_profile.suggestions_size(); if (size == 0) { // No suggestions data available, initiate Top Sites query. InitiateTopSitesQuery(); return; } std::vector titles; std::vector urls; for (size_t i = 0; i < size; ++i) { const ChromeSuggestion& suggestion = suggestions_profile.suggestions(i); titles.push_back(base::UTF8ToUTF16(suggestion.title())); urls.push_back(suggestion.url()); } mv_source_ = SUGGESTIONS_SERVICE; JNIEnv* env = AttachCurrentThread(); Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable( env, j_observer->obj(), ToJavaArrayOfStrings(env, titles).obj(), ToJavaArrayOfStrings(env, urls).obj()); } static jlong Init(JNIEnv* env, jobject obj, jobject jprofile) { MostVisitedSites* most_visited_sites = new MostVisitedSites(ProfileAndroid::FromProfileAndroid(jprofile)); return reinterpret_cast(most_visited_sites); }