// 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 "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/strings/utf_string_conversions.h" #include "base/time/time.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/history/history_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()); } } void 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); JNIEnv* env = AttachCurrentThread(); Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable( env, j_observer->obj(), ToJavaArrayOfStrings(env, titles).obj(), ToJavaArrayOfStrings(env, urls).obj()); } 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 GetUrlThumbnailTask( std::string url_string, scoped_refptr top_sites, ScopedJavaGlobalRef* j_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)); } // 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))); } } // 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)); } } // May be called from any thread void MostVisitedSites::GetURLThumbnail(JNIEnv* env, jobject obj, jstring url, jobject j_callback_obj) { 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()); BrowserThread::PostTask( BrowserThread::DB, FROM_HERE, base::Bind( &GetUrlThumbnailTask, url_string, top_sites, base::Owned(j_callback))); } void MostVisitedSites::BlacklistUrl(JNIEnv* env, jobject obj, jstring j_url) { TopSites* top_sites = profile_->GetTopSites(); if (!top_sites) return; std::string url_string = ConvertJavaStringToUTF8(env, j_url); top_sites->AddBlacklistedURL(GURL(url_string)); } void MostVisitedSites::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK_EQ(type, chrome::NOTIFICATION_TOP_SITES_CHANGED); // Most visited urls changed, query again. QueryMostVisitedURLs(); } // static bool MostVisitedSites::Register(JNIEnv* env) { return RegisterNativesImpl(env); } void MostVisitedSites::QueryMostVisitedURLs() { SuggestionsServiceFactory* suggestions_service_factory = SuggestionsServiceFactory::GetInstance(); SuggestionsService* suggestions_service = suggestions_service_factory->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( &OnMostVisitedURLsAvailable, base::Owned(new ScopedJavaGlobalRef(observer_)), num_sites_), false); } 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()); } 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); }