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