Upstream version 8.37.180.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / android / omnibox / autocomplete_controller_android.cc
1 // Copyright 2014 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/omnibox/autocomplete_controller_android.h"
6
7 #include "base/android/jni_android.h"
8 #include "base/android/jni_string.h"
9 #include "base/prefs/pref_service.h"
10 #include "base/strings/string16.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/time/time.h"
13 #include "base/timer/timer.h"
14 #include "chrome/browser/autocomplete/autocomplete_classifier.h"
15 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
16 #include "chrome/browser/autocomplete/autocomplete_controller.h"
17 #include "chrome/browser/autocomplete/autocomplete_input.h"
18 #include "chrome/browser/autocomplete/autocomplete_match.h"
19 #include "chrome/browser/autocomplete/search_provider.h"
20 #include "chrome/browser/autocomplete/shortcuts_backend_factory.h"
21 #include "chrome/browser/browser_process.h"
22 #include "chrome/browser/chrome_notification_types.h"
23 #include "chrome/browser/omnibox/omnibox_field_trial.h"
24 #include "chrome/browser/omnibox/omnibox_log.h"
25 #include "chrome/browser/profiles/incognito_helpers.h"
26 #include "chrome/browser/profiles/profile_android.h"
27 #include "chrome/browser/profiles/profile_manager.h"
28 #include "chrome/browser/search/search.h"
29 #include "chrome/browser/search_engines/template_url_service.h"
30 #include "chrome/browser/search_engines/template_url_service_factory.h"
31 #include "chrome/browser/sessions/session_id.h"
32 #include "chrome/browser/ui/search/instant_search_prerenderer.h"
33 #include "chrome/browser/ui/toolbar/toolbar_model.h"
34 #include "chrome/common/autocomplete_match_type.h"
35 #include "chrome/common/instant_types.h"
36 #include "chrome/common/pref_names.h"
37 #include "chrome/common/url_constants.h"
38 #include "components/keyed_service/content/browser_context_dependency_manager.h"
39 #include "components/metrics/proto/omnibox_event.pb.h"
40 #include "content/public/browser/notification_details.h"
41 #include "content/public/browser/notification_service.h"
42 #include "content/public/browser/notification_source.h"
43 #include "content/public/browser/web_contents.h"
44 #include "content/public/common/url_constants.h"
45 #include "jni/AutocompleteController_jni.h"
46 #include "net/base/escape.h"
47 #include "net/base/net_util.h"
48 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
49 #include "ui/base/resource/resource_bundle.h"
50
51 using base::android::AttachCurrentThread;
52 using base::android::ConvertJavaStringToUTF16;
53 using base::android::ConvertUTF8ToJavaString;
54 using base::android::ConvertUTF16ToJavaString;
55 using metrics::OmniboxEventProto;
56
57 namespace {
58
59 const int kAndroidAutocompleteProviders =
60     AutocompleteClassifier::kDefaultOmniboxProviders;
61
62 /**
63  * A prefetcher class responsible for triggering zero suggest prefetch.
64  * The prefetch occurs as a side-effect of calling StartZeroSuggest() on
65  * the AutocompleteController object.
66  */
67 class ZeroSuggestPrefetcher : public AutocompleteControllerDelegate {
68  public:
69   explicit ZeroSuggestPrefetcher(Profile* profile);
70
71  private:
72   virtual ~ZeroSuggestPrefetcher();
73   void SelfDestruct();
74
75   // AutocompleteControllerDelegate:
76   virtual void OnResultChanged(bool default_match_changed) OVERRIDE;
77
78   scoped_ptr<AutocompleteController> controller_;
79   base::OneShotTimer<ZeroSuggestPrefetcher> expire_timer_;
80 };
81
82 ZeroSuggestPrefetcher::ZeroSuggestPrefetcher(Profile* profile) : controller_(
83     new AutocompleteController(profile, this,
84                                AutocompleteProvider::TYPE_ZERO_SUGGEST)) {
85   // Creating an arbitrary fake_request_source to avoid passing in an invalid
86   // AutocompleteInput object.
87   base::string16 fake_request_source(base::ASCIIToUTF16(
88       "http://www.foobarbazblah.com"));
89   controller_->StartZeroSuggest(AutocompleteInput(
90       fake_request_source,
91       base::string16::npos,
92       base::string16(),
93       GURL(fake_request_source),
94       OmniboxEventProto::INVALID_SPEC,
95       false,
96       false,
97       true,
98       true));
99   // Delete ourselves after 10s. This is enough time to cache results or
100   // give up if the results haven't been received.
101   expire_timer_.Start(FROM_HERE,
102                       base::TimeDelta::FromMilliseconds(10000),
103                       this, &ZeroSuggestPrefetcher::SelfDestruct);
104 }
105
106 ZeroSuggestPrefetcher::~ZeroSuggestPrefetcher() {
107 }
108
109 void ZeroSuggestPrefetcher::SelfDestruct() {
110   delete this;
111 }
112
113 void ZeroSuggestPrefetcher::OnResultChanged(bool default_match_changed) {
114   // Nothing to do here, the results have been cached.
115   // We don't want to trigger deletion here because this is being called by the
116   // AutocompleteController object.
117 }
118
119 }  // namespace
120
121 AutocompleteControllerAndroid::AutocompleteControllerAndroid(Profile* profile)
122     : autocomplete_controller_(new AutocompleteController(
123           profile, this, kAndroidAutocompleteProviders)),
124       inside_synchronous_start_(false),
125       profile_(profile) {
126 }
127
128 void AutocompleteControllerAndroid::Start(JNIEnv* env,
129                                           jobject obj,
130                                           jstring j_text,
131                                           jstring j_desired_tld,
132                                           jstring j_current_url,
133                                           bool prevent_inline_autocomplete,
134                                           bool prefer_keyword,
135                                           bool allow_exact_keyword_match,
136                                           bool want_asynchronous_matches) {
137   if (!autocomplete_controller_)
138     return;
139
140   base::string16 desired_tld;
141   GURL current_url;
142   if (j_current_url != NULL)
143     current_url = GURL(ConvertJavaStringToUTF16(env, j_current_url));
144   if (j_desired_tld != NULL)
145     desired_tld = ConvertJavaStringToUTF16(env, j_desired_tld);
146   base::string16 text = ConvertJavaStringToUTF16(env, j_text);
147   OmniboxEventProto::PageClassification page_classification =
148       OmniboxEventProto::OTHER;
149   input_ = AutocompleteInput(text,
150                              base::string16::npos,
151                              desired_tld,
152                              current_url,
153                              page_classification,
154                              prevent_inline_autocomplete,
155                              prefer_keyword,
156                              allow_exact_keyword_match,
157                              want_asynchronous_matches);
158   autocomplete_controller_->Start(input_);
159 }
160
161 ScopedJavaLocalRef<jobject> AutocompleteControllerAndroid::Classify(
162     JNIEnv* env,
163     jobject obj,
164     jstring j_text) {
165   return GetTopSynchronousResult(env, obj, j_text, true);
166 }
167
168 void AutocompleteControllerAndroid::StartZeroSuggest(
169     JNIEnv* env,
170     jobject obj,
171     jstring j_omnibox_text,
172     jstring j_current_url,
173     jboolean is_query_in_omnibox,
174     jboolean focused_from_fakebox) {
175   if (!autocomplete_controller_)
176     return;
177
178   base::string16 url = ConvertJavaStringToUTF16(env, j_current_url);
179   const GURL current_url = GURL(url);
180   base::string16 omnibox_text = ConvertJavaStringToUTF16(env, j_omnibox_text);
181
182   // If omnibox text is empty, set it to the current URL for the purposes of
183   // populating the verbatim match.
184   if (omnibox_text.empty())
185     omnibox_text = url;
186
187   input_ = AutocompleteInput(
188       omnibox_text, base::string16::npos, base::string16(), current_url,
189       ClassifyPage(current_url, is_query_in_omnibox, focused_from_fakebox),
190       false, false, true, true);
191   autocomplete_controller_->StartZeroSuggest(input_);
192 }
193
194 void AutocompleteControllerAndroid::Stop(JNIEnv* env,
195                                          jobject obj,
196                                          bool clear_results) {
197   if (autocomplete_controller_ != NULL)
198     autocomplete_controller_->Stop(clear_results);
199 }
200
201 void AutocompleteControllerAndroid::ResetSession(JNIEnv* env, jobject obj) {
202   if (autocomplete_controller_ != NULL)
203     autocomplete_controller_->ResetSession();
204 }
205
206 void AutocompleteControllerAndroid::OnSuggestionSelected(
207     JNIEnv* env,
208     jobject obj,
209     jint selected_index,
210     jstring j_current_url,
211     jboolean is_query_in_omnibox,
212     jboolean focused_from_fakebox,
213     jlong elapsed_time_since_first_modified,
214     jobject j_web_contents) {
215   base::string16 url = ConvertJavaStringToUTF16(env, j_current_url);
216   const GURL current_url = GURL(url);
217   OmniboxEventProto::PageClassification current_page_classification =
218       ClassifyPage(current_url, is_query_in_omnibox, focused_from_fakebox);
219   const base::TimeTicks& now(base::TimeTicks::Now());
220   content::WebContents* web_contents =
221       content::WebContents::FromJavaWebContents(j_web_contents);
222
223   OmniboxLog log(
224       input_.text(),
225       false, /* don't know */
226       input_.type(),
227       true,
228       selected_index,
229       false,
230       SessionID::IdForTab(web_contents),
231       current_page_classification,
232       base::TimeDelta::FromMilliseconds(elapsed_time_since_first_modified),
233       base::string16::npos,
234       now - autocomplete_controller_->last_time_default_match_changed(),
235       autocomplete_controller_->result());
236   autocomplete_controller_->AddProvidersInfo(&log.providers_info);
237
238   content::NotificationService::current()->Notify(
239       chrome::NOTIFICATION_OMNIBOX_OPENED_URL,
240       content::Source<Profile>(profile_),
241       content::Details<OmniboxLog>(&log));
242 }
243
244 void AutocompleteControllerAndroid::DeleteSuggestion(JNIEnv* env,
245                                                      jobject obj,
246                                                      int selected_index) {
247   const AutocompleteResult& result = autocomplete_controller_->result();
248   const AutocompleteMatch& match = result.match_at(selected_index);
249   if (match.SupportsDeletion())
250     autocomplete_controller_->DeleteMatch(match);
251 }
252
253 ScopedJavaLocalRef<jstring>
254 AutocompleteControllerAndroid::UpdateMatchDestinationURL(
255     JNIEnv* env,
256     jobject obj,
257     jint selected_index,
258     jlong elapsed_time_since_input_change) {
259   // In rare cases, we navigate to cached matches and the underlying result
260   // has already been cleared, in that case ignore the URL update.
261   if (autocomplete_controller_->result().empty())
262     return ScopedJavaLocalRef<jstring>();
263
264   AutocompleteMatch match(
265       autocomplete_controller_->result().match_at(selected_index));
266   autocomplete_controller_->UpdateMatchDestinationURL(
267       base::TimeDelta::FromMilliseconds(elapsed_time_since_input_change),
268       &match);
269   return ConvertUTF8ToJavaString(env, match.destination_url.spec());
270 }
271
272 ScopedJavaLocalRef<jobject>
273 AutocompleteControllerAndroid::GetTopSynchronousMatch(JNIEnv* env,
274                                                       jobject obj,
275                                                       jstring query) {
276   return GetTopSynchronousResult(env, obj, query, false);
277 }
278
279 void AutocompleteControllerAndroid::Shutdown() {
280   autocomplete_controller_.reset();
281
282   JNIEnv* env = AttachCurrentThread();
283   ScopedJavaLocalRef<jobject> java_bridge =
284       weak_java_autocomplete_controller_android_.get(env);
285   if (java_bridge.obj())
286     Java_AutocompleteController_notifyNativeDestroyed(env, java_bridge.obj());
287
288   weak_java_autocomplete_controller_android_.reset();
289 }
290
291 // static
292 AutocompleteControllerAndroid*
293 AutocompleteControllerAndroid::Factory::GetForProfile(
294     Profile* profile, JNIEnv* env, jobject obj) {
295   AutocompleteControllerAndroid* bridge =
296       static_cast<AutocompleteControllerAndroid*>(
297           GetInstance()->GetServiceForBrowserContext(profile, true));
298   bridge->InitJNI(env, obj);
299   return bridge;
300 }
301
302 AutocompleteControllerAndroid::Factory*
303 AutocompleteControllerAndroid::Factory::GetInstance() {
304   return Singleton<AutocompleteControllerAndroid::Factory>::get();
305 }
306
307 content::BrowserContext*
308 AutocompleteControllerAndroid::Factory::GetBrowserContextToUse(
309     content::BrowserContext* context) const {
310   return chrome::GetBrowserContextOwnInstanceInIncognito(context);
311 }
312
313 AutocompleteControllerAndroid::Factory::Factory()
314     : BrowserContextKeyedServiceFactory(
315           "AutocompleteControllerAndroid",
316           BrowserContextDependencyManager::GetInstance()) {
317   DependsOn(ShortcutsBackendFactory::GetInstance());
318 }
319
320 AutocompleteControllerAndroid::Factory::~Factory() {
321 }
322
323 KeyedService* AutocompleteControllerAndroid::Factory::BuildServiceInstanceFor(
324     content::BrowserContext* profile) const {
325   return new AutocompleteControllerAndroid(static_cast<Profile*>(profile));
326 }
327
328 AutocompleteControllerAndroid::~AutocompleteControllerAndroid() {
329 }
330
331 void AutocompleteControllerAndroid::InitJNI(JNIEnv* env, jobject obj) {
332   weak_java_autocomplete_controller_android_ =
333       JavaObjectWeakGlobalRef(env, obj);
334 }
335
336 void AutocompleteControllerAndroid::OnResultChanged(
337     bool default_match_changed) {
338   if (!autocomplete_controller_)
339     return;
340
341   const AutocompleteResult& result = autocomplete_controller_->result();
342   const AutocompleteResult::const_iterator default_match(
343       result.default_match());
344   if ((default_match != result.end()) && default_match_changed &&
345       chrome::IsInstantExtendedAPIEnabled() &&
346       chrome::ShouldPrefetchSearchResults()) {
347     InstantSuggestion prefetch_suggestion;
348     // If the default match should be prefetched, do that.
349     if (SearchProvider::ShouldPrefetch(*default_match)) {
350       prefetch_suggestion.text = default_match->contents;
351       prefetch_suggestion.metadata =
352           SearchProvider::GetSuggestMetadata(*default_match);
353     }
354     // Send the prefetch suggestion unconditionally to the Instant search base
355     // page. If there is no suggestion to prefetch, we need to send a blank
356     // query to clear the prefetched results.
357     InstantSearchPrerenderer* prerenderer =
358         InstantSearchPrerenderer::GetForProfile(profile_);
359     if (prerenderer)
360       prerenderer->Prerender(prefetch_suggestion);
361   }
362   if (!inside_synchronous_start_)
363     NotifySuggestionsReceived(autocomplete_controller_->result());
364 }
365
366 void AutocompleteControllerAndroid::NotifySuggestionsReceived(
367     const AutocompleteResult& autocomplete_result) {
368   JNIEnv* env = AttachCurrentThread();
369   ScopedJavaLocalRef<jobject> java_bridge =
370       weak_java_autocomplete_controller_android_.get(env);
371   if (!java_bridge.obj())
372     return;
373
374   ScopedJavaLocalRef<jobject> suggestion_list_obj =
375       Java_AutocompleteController_createOmniboxSuggestionList(
376           env, autocomplete_result.size());
377   for (size_t i = 0; i < autocomplete_result.size(); ++i) {
378     ScopedJavaLocalRef<jobject> j_omnibox_suggestion =
379         BuildOmniboxSuggestion(env, autocomplete_result.match_at(i));
380     Java_AutocompleteController_addOmniboxSuggestionToList(
381         env, suggestion_list_obj.obj(), j_omnibox_suggestion.obj());
382   }
383
384   // Get the inline-autocomplete text.
385   const AutocompleteResult::const_iterator default_match(
386       autocomplete_result.default_match());
387   base::string16 inline_autocomplete_text;
388   if (default_match != autocomplete_result.end()) {
389     inline_autocomplete_text = default_match->inline_autocompletion;
390   }
391   ScopedJavaLocalRef<jstring> inline_text =
392       ConvertUTF16ToJavaString(env, inline_autocomplete_text);
393   jlong j_autocomplete_result =
394       reinterpret_cast<intptr_t>(&(autocomplete_result));
395   Java_AutocompleteController_onSuggestionsReceived(env,
396                                                     java_bridge.obj(),
397                                                     suggestion_list_obj.obj(),
398                                                     inline_text.obj(),
399                                                     j_autocomplete_result);
400 }
401
402 OmniboxEventProto::PageClassification
403 AutocompleteControllerAndroid::ClassifyPage(const GURL& gurl,
404                                             bool is_query_in_omnibox,
405                                             bool focused_from_fakebox) const {
406   if (!gurl.is_valid())
407     return OmniboxEventProto::INVALID_SPEC;
408
409   const std::string& url = gurl.spec();
410
411   if (gurl.SchemeIs(content::kChromeUIScheme) &&
412       gurl.host() == chrome::kChromeUINewTabHost) {
413     return OmniboxEventProto::NTP;
414   }
415
416   if (url == chrome::kChromeUINativeNewTabURL) {
417     return focused_from_fakebox ?
418         OmniboxEventProto::INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS :
419         OmniboxEventProto::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS;
420   }
421
422   if (url == url::kAboutBlankURL)
423     return OmniboxEventProto::BLANK;
424
425   if (url == profile_->GetPrefs()->GetString(prefs::kHomePage))
426     return OmniboxEventProto::HOME_PAGE;
427
428   if (is_query_in_omnibox)
429     return OmniboxEventProto::SEARCH_RESULT_PAGE_DOING_SEARCH_TERM_REPLACEMENT;
430
431   bool is_search_url = TemplateURLServiceFactory::GetForProfile(profile_)->
432       IsSearchResultsPageFromDefaultSearchProvider(gurl);
433   if (is_search_url)
434     return OmniboxEventProto::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT;
435
436   return OmniboxEventProto::OTHER;
437 }
438
439 ScopedJavaLocalRef<jobject>
440 AutocompleteControllerAndroid::BuildOmniboxSuggestion(
441     JNIEnv* env,
442     const AutocompleteMatch& match) {
443   ScopedJavaLocalRef<jstring> contents =
444       ConvertUTF16ToJavaString(env, match.contents);
445   ScopedJavaLocalRef<jstring> description =
446       ConvertUTF16ToJavaString(env, match.description);
447   ScopedJavaLocalRef<jstring> answer_contents =
448       ConvertUTF16ToJavaString(env, match.answer_contents);
449   ScopedJavaLocalRef<jstring> answer_type =
450       ConvertUTF16ToJavaString(env, match.answer_type);
451   ScopedJavaLocalRef<jstring> fill_into_edit =
452       ConvertUTF16ToJavaString(env, match.fill_into_edit);
453   ScopedJavaLocalRef<jstring> destination_url =
454       ConvertUTF8ToJavaString(env, match.destination_url.spec());
455   // Note that we are also removing 'www' host from formatted url.
456   ScopedJavaLocalRef<jstring> formatted_url = ConvertUTF16ToJavaString(env,
457       FormatURLUsingAcceptLanguages(match.stripped_destination_url));
458   return Java_AutocompleteController_buildOmniboxSuggestion(
459       env,
460       match.type,
461       match.relevance,
462       match.transition,
463       contents.obj(),
464       description.obj(),
465       answer_contents.obj(),
466       answer_type.obj(),
467       fill_into_edit.obj(),
468       destination_url.obj(),
469       formatted_url.obj(),
470       match.starred,
471       match.SupportsDeletion());
472 }
473
474 base::string16 AutocompleteControllerAndroid::FormatURLUsingAcceptLanguages(
475     GURL url) {
476   if (profile_ == NULL)
477     return base::string16();
478
479   std::string languages(
480       profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
481
482   return net::FormatUrl(url, languages, net::kFormatUrlOmitAll,
483       net::UnescapeRule::SPACES, NULL, NULL, NULL);
484 }
485
486 ScopedJavaLocalRef<jobject>
487 AutocompleteControllerAndroid::GetTopSynchronousResult(
488     JNIEnv* env,
489     jobject obj,
490     jstring j_text,
491     bool prevent_inline_autocomplete) {
492   if (!autocomplete_controller_)
493     return ScopedJavaLocalRef<jobject>();
494
495   inside_synchronous_start_ = true;
496   Start(env,
497         obj,
498         j_text,
499         NULL,
500         NULL,
501         prevent_inline_autocomplete,
502         false,
503         false,
504         false);
505   inside_synchronous_start_ = false;
506   DCHECK(autocomplete_controller_->done());
507   const AutocompleteResult& result = autocomplete_controller_->result();
508   if (result.empty())
509     return ScopedJavaLocalRef<jobject>();
510
511   return BuildOmniboxSuggestion(env, *result.begin());
512 }
513
514 static jlong Init(JNIEnv* env, jobject obj, jobject jprofile) {
515   Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile);
516   if (!profile)
517     return 0;
518
519   AutocompleteControllerAndroid* native_bridge =
520       AutocompleteControllerAndroid::Factory::GetForProfile(profile, env, obj);
521   return reinterpret_cast<intptr_t>(native_bridge);
522 }
523
524 static jstring QualifyPartialURLQuery(
525     JNIEnv* env, jclass clazz, jstring jquery) {
526   Profile* profile = ProfileManager::GetActiveUserProfile();
527   if (!profile)
528     return NULL;
529   AutocompleteMatch match;
530   base::string16 query_string(ConvertJavaStringToUTF16(env, jquery));
531   AutocompleteClassifierFactory::GetForProfile(profile)->Classify(
532       query_string,
533       false,
534       false,
535       OmniboxEventProto::INVALID_SPEC,
536       &match,
537       NULL);
538   if (!match.destination_url.is_valid())
539     return NULL;
540
541   // Only return a URL if the match is a URL type.
542   if (match.type != AutocompleteMatchType::URL_WHAT_YOU_TYPED &&
543       match.type != AutocompleteMatchType::HISTORY_URL &&
544       match.type != AutocompleteMatchType::NAVSUGGEST)
545     return NULL;
546
547   // As we are returning to Java, it is fine to call Release().
548   return ConvertUTF8ToJavaString(env, match.destination_url.spec()).Release();
549 }
550
551 static void PrefetchZeroSuggestResults(JNIEnv* env, jclass clazz) {
552   Profile* profile = ProfileManager::GetActiveUserProfile();
553   if (!profile)
554     return;
555
556   if (!OmniboxFieldTrial::InZeroSuggestPersonalizedFieldTrial())
557     return;
558
559   // ZeroSuggestPrefetcher deletes itself after it's done prefetching.
560   new ZeroSuggestPrefetcher(profile);
561 }
562
563 // Register native methods
564 bool RegisterAutocompleteControllerAndroid(JNIEnv* env) {
565   return RegisterNativesImpl(env);
566 }