Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / android / most_visited_sites.cc
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/metrics/histogram.h"
16 #include "base/metrics/sparse_histogram.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/time/time.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/history/top_sites.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/profiles/profile_android.h"
25 #include "chrome/browser/search/suggestions/suggestions_service_factory.h"
26 #include "chrome/browser/search/suggestions/suggestions_source.h"
27 #include "chrome/browser/sync/profile_sync_service.h"
28 #include "chrome/browser/sync/profile_sync_service_factory.h"
29 #include "chrome/browser/thumbnails/thumbnail_list_source.h"
30 #include "components/suggestions/suggestions_service.h"
31 #include "components/suggestions/suggestions_utils.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "content/public/browser/notification_source.h"
34 #include "content/public/browser/url_data_source.h"
35 #include "jni/MostVisitedSites_jni.h"
36 #include "third_party/skia/include/core/SkBitmap.h"
37 #include "ui/gfx/android/java_bitmap.h"
38 #include "ui/gfx/codec/jpeg_codec.h"
39
40 using base::android::AttachCurrentThread;
41 using base::android::ConvertUTF8ToJavaString;
42 using base::android::ConvertJavaStringToUTF8;
43 using base::android::ScopedJavaGlobalRef;
44 using base::android::ToJavaArrayOfStrings;
45 using base::android::CheckException;
46 using content::BrowserThread;
47 using history::TopSites;
48 using suggestions::ChromeSuggestion;
49 using suggestions::SuggestionsProfile;
50 using suggestions::SuggestionsService;
51 using suggestions::SuggestionsServiceFactory;
52 using suggestions::SyncState;
53
54 namespace {
55
56 // Total number of tiles displayed.
57 const char kNumTilesHistogramName[] = "NewTabPage.NumberOfTiles";
58 // Tracking thumbnails.
59 const char kNumLocalThumbnailTilesHistogramName[] =
60     "NewTabPage.NumberOfThumbnailTiles";
61 const char kNumEmptyTilesHistogramName[] = "NewTabPage.NumberOfGrayTiles";
62 const char kNumServerTilesHistogramName[] = "NewTabPage.NumberOfExternalTiles";
63 // Client suggestion opened.
64 const char kOpenedItemClientHistogramName[] = "NewTabPage.MostVisited.client";
65 // Control group suggestion opened.
66 const char kOpenedItemControlHistogramName[] = "NewTabPage.MostVisited.client0";
67 // Server suggestion opened, no provider.
68 const char kOpenedItemServerHistogramName[] = "NewTabPage.MostVisited.server";
69 // Server suggestion opened with provider.
70 const char kOpenedItemServerProviderHistogramFormat[] =
71     "NewTabPage.MostVisited.server%d";
72 // Client impression.
73 const char kImpressionClientHistogramName[] =
74     "NewTabPage.SuggestionsImpression.client";
75 // Control group impression.
76 const char kImpressionControlHistogramName[] =
77     "NewTabPage.SuggestionsImpression.client0";
78 // Server suggestion impression, no provider.
79 const char kImpressionServerHistogramName[] =
80     "NewTabPage.SuggestionsImpression.server";
81 // Server suggestion impression with provider.
82 const char kImpressionServerHistogramFormat[] =
83     "NewTabPage.SuggestionsImpression.server%d";
84
85 void ExtractMostVisitedTitlesAndURLs(
86     const history::MostVisitedURLList& visited_list,
87     std::vector<base::string16>* titles,
88     std::vector<std::string>* urls,
89     int num_sites) {
90   size_t max = static_cast<size_t>(num_sites);
91   for (size_t i = 0; i < visited_list.size() && i < max; ++i) {
92     const history::MostVisitedURL& visited = visited_list[i];
93
94     if (visited.url.is_empty())
95       break;  // This is the signal that there are no more real visited sites.
96
97     titles->push_back(visited.title);
98     urls->push_back(visited.url.spec());
99   }
100 }
101
102 SkBitmap ExtractThumbnail(const base::RefCountedMemory& image_data) {
103   scoped_ptr<SkBitmap> image(gfx::JPEGCodec::Decode(
104       image_data.front(),
105       image_data.size()));
106   return image.get() ? *image : SkBitmap();
107 }
108
109 void AddForcedURLOnUIThread(scoped_refptr<history::TopSites> top_sites,
110                             const GURL& url) {
111   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
112   top_sites->AddForcedURL(url, base::Time::Now());
113 }
114
115 // Runs on the DB thread.
116 void GetUrlThumbnailTask(
117     std::string url_string,
118     scoped_refptr<TopSites> top_sites,
119     ScopedJavaGlobalRef<jobject>* j_callback,
120     MostVisitedSites::LookupSuccessCallback lookup_success_ui_callback,
121     base::Closure lookup_failed_ui_callback) {
122   JNIEnv* env = AttachCurrentThread();
123
124   ScopedJavaGlobalRef<jobject>* j_bitmap_ref =
125       new ScopedJavaGlobalRef<jobject>();
126
127   GURL gurl(url_string);
128
129   scoped_refptr<base::RefCountedMemory> data;
130   if (top_sites->GetPageThumbnail(gurl, false, &data)) {
131     SkBitmap thumbnail_bitmap = ExtractThumbnail(*data.get());
132     if (!thumbnail_bitmap.empty()) {
133       j_bitmap_ref->Reset(
134           env,
135           gfx::ConvertToJavaBitmap(&thumbnail_bitmap).obj());
136     }
137   } else {
138     // A thumbnail is not locally available for |gurl|. Make sure it is put in
139     // the list to be fetched at the next visit to this site.
140     BrowserThread::PostTask(
141         BrowserThread::UI, FROM_HERE,
142         base::Bind(AddForcedURLOnUIThread, top_sites, gurl));
143
144     // If appropriate, return on the UI thread to execute the proper callback.
145     if (!lookup_failed_ui_callback.is_null()) {
146       BrowserThread::PostTask(
147           BrowserThread::UI, FROM_HERE, lookup_failed_ui_callback);
148       delete j_bitmap_ref;
149       return;
150     }
151   }
152
153   // Since j_callback is owned by this callback, when the callback falls out of
154   // scope it will be deleted. We need to pass ownership to the next callback.
155   ScopedJavaGlobalRef<jobject>* j_callback_pass =
156       new ScopedJavaGlobalRef<jobject>(*j_callback);
157   BrowserThread::PostTask(
158       BrowserThread::UI, FROM_HERE,
159       base::Bind(lookup_success_ui_callback, base::Owned(j_bitmap_ref),
160                  base::Owned(j_callback_pass)));
161 }
162
163 // Log an event for a given |histogram| at a given element |position|. This
164 // routine exists because regular histogram macros are cached thus can't be used
165 // if the name of the histogram will change at a given call site.
166 void LogHistogramEvent(const std::string& histogram, int position,
167                        int num_sites) {
168   base::HistogramBase* counter = base::LinearHistogram::FactoryGet(
169       histogram,
170       1,
171       num_sites,
172       num_sites + 1,
173       base::Histogram::kUmaTargetedHistogramFlag);
174   if (counter)
175     counter->Add(position);
176 }
177
178 // Return the current SyncState for use with the SuggestionsService.
179 SyncState GetSyncState(Profile* profile) {
180   ProfileSyncService* sync =
181       ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
182   if (!sync)
183     return SyncState::SYNC_OR_HISTORY_SYNC_DISABLED;
184   return suggestions::GetSyncState(
185       sync->IsSyncEnabledAndLoggedIn(),
186       sync->SyncActive(),
187       sync->GetActiveDataTypes().Has(syncer::HISTORY_DELETE_DIRECTIVES));
188 }
189
190 }  // namespace
191
192 MostVisitedSites::MostVisitedSites(Profile* profile)
193     : profile_(profile), num_sites_(0), is_control_group_(false),
194       initial_load_done_(false), num_local_thumbs_(0), num_server_thumbs_(0),
195       num_empty_thumbs_(0), weak_ptr_factory_(this) {
196   // Register the debugging page for the Suggestions Service and the thumbnails
197   // debugging page.
198   content::URLDataSource::Add(profile_,
199                               new suggestions::SuggestionsSource(profile_));
200   content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_));
201
202   // Register this class as an observer to the sync service. It is important to
203   // be notified of changes in the sync state such as initialization, sync
204   // being enabled or disabled, etc.
205   ProfileSyncService* profile_sync_service =
206       ProfileSyncServiceFactory::GetForProfile(profile_);
207   if (profile_sync_service)
208     profile_sync_service->AddObserver(this);
209 }
210
211 MostVisitedSites::~MostVisitedSites() {
212   ProfileSyncService* profile_sync_service =
213       ProfileSyncServiceFactory::GetForProfile(profile_);
214   if (profile_sync_service && profile_sync_service->HasObserver(this))
215     profile_sync_service->RemoveObserver(this);
216 }
217
218 void MostVisitedSites::Destroy(JNIEnv* env, jobject obj) {
219   delete this;
220 }
221
222 void MostVisitedSites::OnLoadingComplete(JNIEnv* env, jobject obj) {
223   RecordUMAMetrics();
224 }
225
226 void MostVisitedSites::SetMostVisitedURLsObserver(JNIEnv* env,
227                                                   jobject obj,
228                                                   jobject j_observer,
229                                                   jint num_sites) {
230   observer_.Reset(env, j_observer);
231   num_sites_ = num_sites;
232
233   QueryMostVisitedURLs();
234
235   history::TopSites* top_sites = profile_->GetTopSites();
236   if (top_sites) {
237     // TopSites updates itself after a delay. To ensure up-to-date results,
238     // force an update now.
239     top_sites->SyncWithHistory();
240
241     // Register for notification when TopSites changes so that we can update
242     // ourself.
243     registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED,
244                    content::Source<history::TopSites>(top_sites));
245   }
246 }
247
248 // Called from the UI Thread.
249 void MostVisitedSites::GetURLThumbnail(JNIEnv* env,
250                                        jobject obj,
251                                        jstring url,
252                                        jobject j_callback_obj) {
253   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
254   ScopedJavaGlobalRef<jobject>* j_callback =
255       new ScopedJavaGlobalRef<jobject>();
256   j_callback->Reset(env, j_callback_obj);
257
258   std::string url_string = ConvertJavaStringToUTF8(env, url);
259   scoped_refptr<TopSites> top_sites(profile_->GetTopSites());
260
261   // If the Suggestions service is enabled and in use, create a callback to
262   // fetch a server thumbnail from it, in case the local thumbnail is not found.
263   SuggestionsService* suggestions_service =
264       SuggestionsServiceFactory::GetForProfile(profile_);
265   bool use_suggestions_service = suggestions_service &&
266       mv_source_ == SUGGESTIONS_SERVICE;
267   base::Closure lookup_failed_callback = use_suggestions_service ?
268       base::Bind(&MostVisitedSites::GetSuggestionsThumbnailOnUIThread,
269                  weak_ptr_factory_.GetWeakPtr(),
270                  suggestions_service, url_string,
271                  base::Owned(new ScopedJavaGlobalRef<jobject>(*j_callback))) :
272       base::Closure();
273   LookupSuccessCallback lookup_success_callback =
274       base::Bind(&MostVisitedSites::OnObtainedThumbnail,
275                  weak_ptr_factory_.GetWeakPtr());
276
277   BrowserThread::PostTask(
278       BrowserThread::DB, FROM_HERE,
279           base::Bind(
280               &GetUrlThumbnailTask, url_string, top_sites,
281               base::Owned(j_callback), lookup_success_callback,
282               lookup_failed_callback));
283 }
284
285 void MostVisitedSites::BlacklistUrl(JNIEnv* env,
286                                     jobject obj,
287                                     jstring j_url) {
288   std::string url = ConvertJavaStringToUTF8(env, j_url);
289
290   switch (mv_source_) {
291     case TOP_SITES: {
292       TopSites* top_sites = profile_->GetTopSites();
293       DCHECK(top_sites);
294       top_sites->AddBlacklistedURL(GURL(url));
295       break;
296     }
297
298     case SUGGESTIONS_SERVICE: {
299       SuggestionsService* suggestions_service =
300           SuggestionsServiceFactory::GetForProfile(profile_);
301       DCHECK(suggestions_service);
302       suggestions_service->BlacklistURL(
303           GURL(url),
304           base::Bind(
305               &MostVisitedSites::OnSuggestionsProfileAvailable,
306               weak_ptr_factory_.GetWeakPtr(),
307               base::Owned(new ScopedJavaGlobalRef<jobject>(observer_))));
308       break;
309     }
310   }
311 }
312
313 void MostVisitedSites::RecordOpenedMostVisitedItem(JNIEnv* env,
314                                                    jobject obj,
315                                                    jint index) {
316   switch (mv_source_) {
317     case TOP_SITES: {
318       const std::string histogram = is_control_group_ ?
319           kOpenedItemControlHistogramName : kOpenedItemClientHistogramName;
320       LogHistogramEvent(histogram, index, num_sites_);
321       break;
322     }
323     case SUGGESTIONS_SERVICE: {
324       if (server_suggestions_.suggestions_size() > index) {
325         if (server_suggestions_.suggestions(index).providers_size()) {
326           std::string histogram = base::StringPrintf(
327               kOpenedItemServerProviderHistogramFormat,
328               server_suggestions_.suggestions(index).providers(0));
329           LogHistogramEvent(histogram, index, num_sites_);
330         } else {
331           UMA_HISTOGRAM_SPARSE_SLOWLY(kOpenedItemServerHistogramName, index);
332         }
333       }
334       break;
335     }
336   }
337 }
338
339 void MostVisitedSites::Observe(int type,
340                                const content::NotificationSource& source,
341                                const content::NotificationDetails& details) {
342   DCHECK_EQ(type, chrome::NOTIFICATION_TOP_SITES_CHANGED);
343
344   if (mv_source_ == TOP_SITES) {
345     // The displayed suggestions are invalidated.
346     QueryMostVisitedURLs();
347   }
348 }
349
350 void MostVisitedSites::OnStateChanged() {
351   // There have been changes to the sync state. This class cares about a few
352   // (just initialized, enabled/disabled or history sync state changed). Re-run
353   // the query code which will use the proper state.
354   QueryMostVisitedURLs();
355 }
356
357 // static
358 bool MostVisitedSites::Register(JNIEnv* env) {
359   return RegisterNativesImpl(env);
360 }
361
362 void MostVisitedSites::QueryMostVisitedURLs() {
363   SuggestionsService* suggestions_service =
364       SuggestionsServiceFactory::GetForProfile(profile_);
365   if (suggestions_service) {
366     // Suggestions service is enabled, initiate a query.
367     suggestions_service->FetchSuggestionsData(
368         GetSyncState(profile_),
369         base::Bind(
370           &MostVisitedSites::OnSuggestionsProfileAvailable,
371           weak_ptr_factory_.GetWeakPtr(),
372           base::Owned(new ScopedJavaGlobalRef<jobject>(observer_))));
373   } else {
374     InitiateTopSitesQuery();
375   }
376 }
377
378 void MostVisitedSites::InitiateTopSitesQuery() {
379   TopSites* top_sites = profile_->GetTopSites();
380   if (!top_sites)
381     return;
382
383   top_sites->GetMostVisitedURLs(
384       base::Bind(
385           &MostVisitedSites::OnMostVisitedURLsAvailable,
386           weak_ptr_factory_.GetWeakPtr(),
387           base::Owned(new ScopedJavaGlobalRef<jobject>(observer_)),
388           num_sites_),
389       false);
390 }
391
392 void MostVisitedSites::OnMostVisitedURLsAvailable(
393     ScopedJavaGlobalRef<jobject>* j_observer,
394     int num_sites,
395     const history::MostVisitedURLList& visited_list) {
396   std::vector<base::string16> titles;
397   std::vector<std::string> urls;
398   ExtractMostVisitedTitlesAndURLs(visited_list, &titles, &urls, num_sites);
399
400   mv_source_ = TOP_SITES;
401
402   // Only log impression metrics on the initial load of the NTP.
403   if (!initial_load_done_) {
404     int num_tiles = urls.size();
405     UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName, num_tiles);
406     const std::string histogram = is_control_group_ ?
407         kImpressionControlHistogramName : kImpressionClientHistogramName;
408     for (int i = 0; i < num_tiles; ++i) {
409       LogHistogramEvent(histogram, i, num_sites_);
410     }
411   }
412   initial_load_done_ = true;
413
414   JNIEnv* env = AttachCurrentThread();
415   Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
416       env,
417       j_observer->obj(),
418       ToJavaArrayOfStrings(env, titles).obj(),
419       ToJavaArrayOfStrings(env, urls).obj());
420 }
421
422 void MostVisitedSites::OnSuggestionsProfileAvailable(
423     ScopedJavaGlobalRef<jobject>* j_observer,
424     const SuggestionsProfile& suggestions_profile) {
425   int size = suggestions_profile.suggestions_size();
426
427   // Determine if the user is in a control group (they would have received
428   // suggestions, but are in a group where they shouldn't).
429   is_control_group_ = size && SuggestionsService::IsControlGroup();
430
431   // If no suggestions data is available or the user is in a control group,
432   // initiate Top Sites query.
433   if (is_control_group_ || !size) {
434     InitiateTopSitesQuery();
435     return;
436   }
437
438   std::vector<base::string16> titles;
439   std::vector<std::string> urls;
440
441   int i = 0;
442   for (; i < size && i < num_sites_; ++i) {
443     const ChromeSuggestion& suggestion = suggestions_profile.suggestions(i);
444     titles.push_back(base::UTF8ToUTF16(suggestion.title()));
445     urls.push_back(suggestion.url());
446     // Only log impression metrics on the initial NTP load.
447     if (!initial_load_done_) {
448       if (suggestion.providers_size()) {
449         std::string histogram = base::StringPrintf(
450             kImpressionServerHistogramFormat, suggestion.providers(0));
451         LogHistogramEvent(histogram, i, num_sites_);
452       } else {
453         UMA_HISTOGRAM_SPARSE_SLOWLY(kImpressionServerHistogramName, i);
454       }
455     }
456   }
457   if (!initial_load_done_) {
458     UMA_HISTOGRAM_SPARSE_SLOWLY(kNumTilesHistogramName, i);
459   }
460   initial_load_done_ = true;
461
462   mv_source_ = SUGGESTIONS_SERVICE;
463   // Keep a copy of the suggestions for eventual logging.
464   server_suggestions_ = suggestions_profile;
465
466   JNIEnv* env = AttachCurrentThread();
467   Java_MostVisitedURLsObserver_onMostVisitedURLsAvailable(
468       env,
469       j_observer->obj(),
470       ToJavaArrayOfStrings(env, titles).obj(),
471       ToJavaArrayOfStrings(env, urls).obj());
472 }
473
474 void MostVisitedSites::OnObtainedThumbnail(
475     ScopedJavaGlobalRef<jobject>* bitmap,
476     ScopedJavaGlobalRef<jobject>* j_callback) {
477   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
478   JNIEnv* env = AttachCurrentThread();
479   if (bitmap->obj()) {
480     num_local_thumbs_++;
481   } else {
482     num_empty_thumbs_++;
483   }
484   Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
485       env, j_callback->obj(), bitmap->obj());
486 }
487
488 void MostVisitedSites::GetSuggestionsThumbnailOnUIThread(
489     SuggestionsService* suggestions_service,
490     const std::string& url_string,
491     ScopedJavaGlobalRef<jobject>* j_callback) {
492   suggestions_service->GetPageThumbnail(
493       GURL(url_string),
494       base::Bind(&MostVisitedSites::OnSuggestionsThumbnailAvailable,
495                  weak_ptr_factory_.GetWeakPtr(),
496                  base::Owned(new ScopedJavaGlobalRef<jobject>(*j_callback))));
497 }
498
499 void MostVisitedSites::OnSuggestionsThumbnailAvailable(
500     ScopedJavaGlobalRef<jobject>* j_callback,
501     const GURL& url,
502     const SkBitmap* bitmap) {
503   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
504   JNIEnv* env = AttachCurrentThread();
505
506   ScopedJavaGlobalRef<jobject>* j_bitmap_ref =
507       new ScopedJavaGlobalRef<jobject>();
508   if (bitmap) {
509     num_server_thumbs_++;
510     j_bitmap_ref->Reset(
511         env,
512         gfx::ConvertToJavaBitmap(bitmap).obj());
513   } else {
514     num_empty_thumbs_++;
515   }
516
517   Java_ThumbnailCallback_onMostVisitedURLsThumbnailAvailable(
518       env, j_callback->obj(), j_bitmap_ref->obj());
519 }
520
521 void MostVisitedSites::RecordUMAMetrics() {
522   UMA_HISTOGRAM_SPARSE_SLOWLY(kNumLocalThumbnailTilesHistogramName,
523                               num_local_thumbs_);
524   num_local_thumbs_ = 0;
525   UMA_HISTOGRAM_SPARSE_SLOWLY(kNumEmptyTilesHistogramName, num_empty_thumbs_);
526   num_empty_thumbs_ = 0;
527   UMA_HISTOGRAM_SPARSE_SLOWLY(kNumServerTilesHistogramName, num_server_thumbs_);
528   num_server_thumbs_ = 0;
529 }
530
531 static jlong Init(JNIEnv* env, jobject obj, jobject jprofile) {
532   MostVisitedSites* most_visited_sites =
533       new MostVisitedSites(ProfileAndroid::FromProfileAndroid(jprofile));
534   return reinterpret_cast<intptr_t>(most_visited_sites);
535 }