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 "base/android/jni_android.h"
8#include "base/android/jni_array.h"
9#include "base/android/jni_string.h"
10#include "base/android/scoped_java_ref.h"
11#include "chrome/browser/chrome_notification_types.h"
12#include "chrome/browser/history/history_types.h"
13#include "chrome/browser/history/top_sites.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/profiles/profile_android.h"
16#include "content/public/browser/browser_thread.h"
17#include "content/public/browser/notification_source.h"
18#include "jni/MostVisitedSites_jni.h"
19#include "third_party/skia/include/core/SkBitmap.h"
20#include "ui/gfx/android/java_bitmap.h"
21#include "ui/gfx/codec/jpeg_codec.h"
22
23using base::android::AttachCurrentThread;
24using base::android::ConvertUTF8ToJavaString;
25using base::android::ConvertJavaStringToUTF8;
26using base::android::ScopedJavaGlobalRef;
27using base::android::ToJavaArrayOfStrings;
28using base::android::CheckException;
29using content::BrowserThread;
30using history::TopSites;
31
32namespace {
33
34void ExtractMostVisitedTitlesAndURLs(
35    const history::MostVisitedURLList& visited_list,
36    std::vector<base::string16>* titles,
37    std::vector<std::string>* urls,
38    int num_sites) {
39  size_t max = static_cast<size_t>(num_sites);
40  for (size_t i = 0; i < visited_list.size() && i < max; ++i) {
41    const history::MostVisitedURL& visited = visited_list[i];
42
43    if (visited.url.is_empty())
44      break;  // This is the signal that there are no more real visited sites.
45
46    titles->push_back(visited.title);
47    urls->push_back(visited.url.spec());
48  }
49}
50
51void OnMostVisitedURLsAvailable(
52    ScopedJavaGlobalRef<jobject>* j_observer,
53    int num_sites,
54    const history::MostVisitedURLList& visited_list) {
55  std::vector<base::string16> titles;
56  std::vector<std::string> urls;
57  ExtractMostVisitedTitlesAndURLs(visited_list, &titles, &urls, num_sites);
58
59  JNIEnv* env = AttachCurrentThread();
60  Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
61      env,
62      j_observer->obj(),
63      ToJavaArrayOfStrings(env, titles).obj(),
64      ToJavaArrayOfStrings(env, urls).obj());
65}
66
67SkBitmap ExtractThumbnail(const base::RefCountedMemory& image_data) {
68  scoped_ptr<SkBitmap> image(gfx::JPEGCodec::Decode(
69      image_data.front(),
70      image_data.size()));
71  return image.get() ? *image : SkBitmap();
72}
73
74void OnObtainedThumbnail(
75    ScopedJavaGlobalRef<jobject>* bitmap,
76    ScopedJavaGlobalRef<jobject>* j_callback) {
77  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
78  JNIEnv* env = AttachCurrentThread();
79  Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
80      env, j_callback->obj(), bitmap->obj());
81}
82
83void GetUrlThumbnailTask(
84    std::string url_string,
85    scoped_refptr<TopSites> top_sites,
86    ScopedJavaGlobalRef<jobject>* j_callback) {
87  JNIEnv* env = AttachCurrentThread();
88
89  ScopedJavaGlobalRef<jobject>* j_bitmap_ref =
90      new ScopedJavaGlobalRef<jobject>();
91
92  GURL gurl(url_string);
93
94  scoped_refptr<base::RefCountedMemory> data;
95  if (top_sites->GetPageThumbnail(gurl, false, &data)) {
96    SkBitmap thumbnail_bitmap = ExtractThumbnail(*data.get());
97    if (!thumbnail_bitmap.empty()) {
98      j_bitmap_ref->Reset(
99          env,
100          gfx::ConvertToJavaBitmap(&thumbnail_bitmap).obj());
101    }
102  }
103
104  // Since j_callback is owned by this callback, when the callback falls out of
105  // scope it will be deleted. We need to pass ownership to the next callback.
106  ScopedJavaGlobalRef<jobject>* j_callback_pass =
107      new ScopedJavaGlobalRef<jobject>(*j_callback);
108  BrowserThread::PostTask(
109      BrowserThread::UI, FROM_HERE,
110      base::Bind(
111          &OnObtainedThumbnail,
112          base::Owned(j_bitmap_ref), base::Owned(j_callback_pass)));
113}
114
115}  // namespace
116
117MostVisitedSites::MostVisitedSites(Profile* profile)
118    : profile_(profile), num_sites_(0) {
119}
120
121MostVisitedSites::~MostVisitedSites() {
122}
123
124void MostVisitedSites::Destroy(JNIEnv* env, jobject obj) {
125  delete this;
126}
127
128void MostVisitedSites::SetMostVisitedURLsObserver(JNIEnv* env,
129                                                  jobject obj,
130                                                  jobject j_observer,
131                                                  jint num_sites) {
132  observer_.Reset(env, j_observer);
133  num_sites_ = num_sites;
134
135  QueryMostVisitedURLs();
136
137  history::TopSites* top_sites = profile_->GetTopSites();
138  if (top_sites) {
139    // TopSites updates itself after a delay. To ensure up-to-date results,
140    // force an update now.
141    top_sites->SyncWithHistory();
142
143    // Register for notification when TopSites changes so that we can update
144    // ourself.
145    registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED,
146                   content::Source<history::TopSites>(top_sites));
147  }
148}
149
150// May be called from any thread
151void MostVisitedSites::GetURLThumbnail(JNIEnv* env,
152                                       jobject obj,
153                                       jstring url,
154                                       jobject j_callback_obj) {
155  ScopedJavaGlobalRef<jobject>* j_callback =
156      new ScopedJavaGlobalRef<jobject>();
157  j_callback->Reset(env, j_callback_obj);
158
159  std::string url_string = ConvertJavaStringToUTF8(env, url);
160  scoped_refptr<TopSites> top_sites(profile_->GetTopSites());
161  BrowserThread::PostTask(
162      BrowserThread::DB, FROM_HERE, base::Bind(
163          &GetUrlThumbnailTask,
164          url_string,
165          top_sites, base::Owned(j_callback)));
166}
167
168void MostVisitedSites::BlacklistUrl(JNIEnv* env,
169                                    jobject obj,
170                                    jstring j_url) {
171  TopSites* top_sites = profile_->GetTopSites();
172  if (!top_sites)
173    return;
174
175  std::string url_string = ConvertJavaStringToUTF8(env, j_url);
176  top_sites->AddBlacklistedURL(GURL(url_string));
177}
178
179void MostVisitedSites::Observe(int type,
180                               const content::NotificationSource& source,
181                               const content::NotificationDetails& details) {
182  DCHECK_EQ(type, chrome::NOTIFICATION_TOP_SITES_CHANGED);
183
184  // Most visited urls changed, query again.
185  QueryMostVisitedURLs();
186}
187
188// static
189bool MostVisitedSites::Register(JNIEnv* env) {
190  return RegisterNativesImpl(env);
191}
192
193void MostVisitedSites::QueryMostVisitedURLs() {
194  TopSites* top_sites = profile_->GetTopSites();
195  if (!top_sites)
196    return;
197
198  top_sites->GetMostVisitedURLs(
199      base::Bind(
200          &OnMostVisitedURLsAvailable,
201          base::Owned(new ScopedJavaGlobalRef<jobject>(observer_)),
202          num_sites_),
203      false);
204}
205
206static jlong Init(JNIEnv* env, jobject obj, jobject jprofile) {
207  MostVisitedSites* most_visited_sites =
208      new MostVisitedSites(ProfileAndroid::FromProfileAndroid(jprofile));
209  return reinterpret_cast<intptr_t>(most_visited_sites);
210}
211