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.
5 #include "chrome/browser/android/omnibox/autocomplete_controller_android.h"
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"
51 using base::android::AttachCurrentThread;
52 using base::android::ConvertJavaStringToUTF16;
53 using base::android::ConvertUTF8ToJavaString;
54 using base::android::ConvertUTF16ToJavaString;
55 using metrics::OmniboxEventProto;
59 const int kAndroidAutocompleteProviders =
60 AutocompleteClassifier::kDefaultOmniboxProviders;
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.
67 class ZeroSuggestPrefetcher : public AutocompleteControllerDelegate {
69 explicit ZeroSuggestPrefetcher(Profile* profile);
72 virtual ~ZeroSuggestPrefetcher();
75 // AutocompleteControllerDelegate:
76 virtual void OnResultChanged(bool default_match_changed) OVERRIDE;
78 scoped_ptr<AutocompleteController> controller_;
79 base::OneShotTimer<ZeroSuggestPrefetcher> expire_timer_;
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(
93 GURL(fake_request_source),
94 OmniboxEventProto::INVALID_SPEC,
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);
106 ZeroSuggestPrefetcher::~ZeroSuggestPrefetcher() {
109 void ZeroSuggestPrefetcher::SelfDestruct() {
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.
121 AutocompleteControllerAndroid::AutocompleteControllerAndroid(Profile* profile)
122 : autocomplete_controller_(new AutocompleteController(
123 profile, this, kAndroidAutocompleteProviders)),
124 inside_synchronous_start_(false),
128 void AutocompleteControllerAndroid::Start(JNIEnv* env,
131 jstring j_desired_tld,
132 jstring j_current_url,
133 bool prevent_inline_autocomplete,
135 bool allow_exact_keyword_match,
136 bool want_asynchronous_matches) {
137 if (!autocomplete_controller_)
140 base::string16 desired_tld;
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,
154 prevent_inline_autocomplete,
156 allow_exact_keyword_match,
157 want_asynchronous_matches);
158 autocomplete_controller_->Start(input_);
161 ScopedJavaLocalRef<jobject> AutocompleteControllerAndroid::Classify(
165 return GetTopSynchronousResult(env, obj, j_text, true);
168 void AutocompleteControllerAndroid::StartZeroSuggest(
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_)
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);
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())
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_);
194 void AutocompleteControllerAndroid::Stop(JNIEnv* env,
196 bool clear_results) {
197 if (autocomplete_controller_ != NULL)
198 autocomplete_controller_->Stop(clear_results);
201 void AutocompleteControllerAndroid::ResetSession(JNIEnv* env, jobject obj) {
202 if (autocomplete_controller_ != NULL)
203 autocomplete_controller_->ResetSession();
206 void AutocompleteControllerAndroid::OnSuggestionSelected(
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);
225 false, /* don't know */
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);
238 content::NotificationService::current()->Notify(
239 chrome::NOTIFICATION_OMNIBOX_OPENED_URL,
240 content::Source<Profile>(profile_),
241 content::Details<OmniboxLog>(&log));
244 void AutocompleteControllerAndroid::DeleteSuggestion(JNIEnv* env,
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);
253 ScopedJavaLocalRef<jstring>
254 AutocompleteControllerAndroid::UpdateMatchDestinationURL(
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>();
264 AutocompleteMatch match(
265 autocomplete_controller_->result().match_at(selected_index));
266 autocomplete_controller_->UpdateMatchDestinationURL(
267 base::TimeDelta::FromMilliseconds(elapsed_time_since_input_change),
269 return ConvertUTF8ToJavaString(env, match.destination_url.spec());
272 ScopedJavaLocalRef<jobject>
273 AutocompleteControllerAndroid::GetTopSynchronousMatch(JNIEnv* env,
276 return GetTopSynchronousResult(env, obj, query, false);
279 void AutocompleteControllerAndroid::Shutdown() {
280 autocomplete_controller_.reset();
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());
288 weak_java_autocomplete_controller_android_.reset();
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);
302 AutocompleteControllerAndroid::Factory*
303 AutocompleteControllerAndroid::Factory::GetInstance() {
304 return Singleton<AutocompleteControllerAndroid::Factory>::get();
307 content::BrowserContext*
308 AutocompleteControllerAndroid::Factory::GetBrowserContextToUse(
309 content::BrowserContext* context) const {
310 return chrome::GetBrowserContextOwnInstanceInIncognito(context);
313 AutocompleteControllerAndroid::Factory::Factory()
314 : BrowserContextKeyedServiceFactory(
315 "AutocompleteControllerAndroid",
316 BrowserContextDependencyManager::GetInstance()) {
317 DependsOn(ShortcutsBackendFactory::GetInstance());
320 AutocompleteControllerAndroid::Factory::~Factory() {
323 KeyedService* AutocompleteControllerAndroid::Factory::BuildServiceInstanceFor(
324 content::BrowserContext* profile) const {
325 return new AutocompleteControllerAndroid(static_cast<Profile*>(profile));
328 AutocompleteControllerAndroid::~AutocompleteControllerAndroid() {
331 void AutocompleteControllerAndroid::InitJNI(JNIEnv* env, jobject obj) {
332 weak_java_autocomplete_controller_android_ =
333 JavaObjectWeakGlobalRef(env, obj);
336 void AutocompleteControllerAndroid::OnResultChanged(
337 bool default_match_changed) {
338 if (!autocomplete_controller_)
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);
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_);
360 prerenderer->Prerender(prefetch_suggestion);
362 if (!inside_synchronous_start_)
363 NotifySuggestionsReceived(autocomplete_controller_->result());
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())
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());
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;
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,
397 suggestion_list_obj.obj(),
399 j_autocomplete_result);
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;
409 const std::string& url = gurl.spec();
411 if (gurl.SchemeIs(content::kChromeUIScheme) &&
412 gurl.host() == chrome::kChromeUINewTabHost) {
413 return OmniboxEventProto::NTP;
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;
422 if (url == url::kAboutBlankURL)
423 return OmniboxEventProto::BLANK;
425 if (url == profile_->GetPrefs()->GetString(prefs::kHomePage))
426 return OmniboxEventProto::HOME_PAGE;
428 if (is_query_in_omnibox)
429 return OmniboxEventProto::SEARCH_RESULT_PAGE_DOING_SEARCH_TERM_REPLACEMENT;
431 bool is_search_url = TemplateURLServiceFactory::GetForProfile(profile_)->
432 IsSearchResultsPageFromDefaultSearchProvider(gurl);
434 return OmniboxEventProto::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT;
436 return OmniboxEventProto::OTHER;
439 ScopedJavaLocalRef<jobject>
440 AutocompleteControllerAndroid::BuildOmniboxSuggestion(
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(
465 answer_contents.obj(),
467 fill_into_edit.obj(),
468 destination_url.obj(),
471 match.SupportsDeletion());
474 base::string16 AutocompleteControllerAndroid::FormatURLUsingAcceptLanguages(
476 if (profile_ == NULL)
477 return base::string16();
479 std::string languages(
480 profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
482 return net::FormatUrl(url, languages, net::kFormatUrlOmitAll,
483 net::UnescapeRule::SPACES, NULL, NULL, NULL);
486 ScopedJavaLocalRef<jobject>
487 AutocompleteControllerAndroid::GetTopSynchronousResult(
491 bool prevent_inline_autocomplete) {
492 if (!autocomplete_controller_)
493 return ScopedJavaLocalRef<jobject>();
495 inside_synchronous_start_ = true;
501 prevent_inline_autocomplete,
505 inside_synchronous_start_ = false;
506 DCHECK(autocomplete_controller_->done());
507 const AutocompleteResult& result = autocomplete_controller_->result();
509 return ScopedJavaLocalRef<jobject>();
511 return BuildOmniboxSuggestion(env, *result.begin());
514 static jlong Init(JNIEnv* env, jobject obj, jobject jprofile) {
515 Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile);
519 AutocompleteControllerAndroid* native_bridge =
520 AutocompleteControllerAndroid::Factory::GetForProfile(profile, env, obj);
521 return reinterpret_cast<intptr_t>(native_bridge);
524 static jstring QualifyPartialURLQuery(
525 JNIEnv* env, jclass clazz, jstring jquery) {
526 Profile* profile = ProfileManager::GetActiveUserProfile();
529 AutocompleteMatch match;
530 base::string16 query_string(ConvertJavaStringToUTF16(env, jquery));
531 AutocompleteClassifierFactory::GetForProfile(profile)->Classify(
535 OmniboxEventProto::INVALID_SPEC,
538 if (!match.destination_url.is_valid())
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)
547 // As we are returning to Java, it is fine to call Release().
548 return ConvertUTF8ToJavaString(env, match.destination_url.spec()).Release();
551 static void PrefetchZeroSuggestResults(JNIEnv* env, jclass clazz) {
552 Profile* profile = ProfileManager::GetActiveUserProfile();
556 if (!OmniboxFieldTrial::InZeroSuggestPersonalizedFieldTrial())
559 // ZeroSuggestPrefetcher deletes itself after it's done prefetching.
560 new ZeroSuggestPrefetcher(profile);
563 // Register native methods
564 bool RegisterAutocompleteControllerAndroid(JNIEnv* env) {
565 return RegisterNativesImpl(env);