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_factory.h"
15 #include "chrome/browser/autocomplete/chrome_autocomplete_provider_client.h"
16 #include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
17 #include "chrome/browser/autocomplete/shortcuts_backend_factory.h"
18 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
19 #include "chrome/browser/browser_process.h"
20 #include "chrome/browser/predictors/autocomplete_action_predictor.h"
21 #include "chrome/browser/predictors/autocomplete_action_predictor_factory.h"
22 #include "chrome/browser/profiles/incognito_helpers.h"
23 #include "chrome/browser/profiles/profile_android.h"
24 #include "chrome/browser/profiles/profile_manager.h"
25 #include "chrome/browser/search/search.h"
26 #include "chrome/browser/search_engines/template_url_service_factory.h"
27 #include "chrome/browser/sessions/session_tab_helper.h"
28 #include "chrome/browser/ui/search/instant_search_prerenderer.h"
29 #include "chrome/common/instant_types.h"
30 #include "chrome/common/pref_names.h"
31 #include "chrome/common/url_constants.h"
32 #include "components/bookmarks/browser/bookmark_model.h"
33 #include "components/keyed_service/content/browser_context_dependency_manager.h"
34 #include "components/metrics/proto/omnibox_event.pb.h"
35 #include "components/omnibox/browser/autocomplete_classifier.h"
36 #include "components/omnibox/browser/autocomplete_controller.h"
37 #include "components/omnibox/browser/autocomplete_input.h"
38 #include "components/omnibox/browser/autocomplete_match.h"
39 #include "components/omnibox/browser/autocomplete_match_type.h"
40 #include "components/omnibox/browser/omnibox_event_global_tracker.h"
41 #include "components/omnibox/browser/omnibox_field_trial.h"
42 #include "components/omnibox/browser/omnibox_log.h"
43 #include "components/omnibox/browser/search_provider.h"
44 #include "components/search/search.h"
45 #include "components/search_engines/template_url_service.h"
46 #include "components/toolbar/toolbar_model.h"
47 #include "components/url_formatter/url_formatter.h"
48 #include "content/public/browser/web_contents.h"
49 #include "content/public/common/url_constants.h"
50 #include "jni/AutocompleteController_jni.h"
51 #include "net/base/escape.h"
52 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
54 using base::android::AttachCurrentThread
;
55 using base::android::ConvertJavaStringToUTF16
;
56 using base::android::ConvertUTF8ToJavaString
;
57 using base::android::ConvertUTF16ToJavaString
;
58 using bookmarks::BookmarkModel
;
59 using metrics::OmniboxEventProto
;
63 const int kAndroidAutocompleteProviders
=
64 AutocompleteClassifier::kDefaultOmniboxProviders
;
67 * A prefetcher class responsible for triggering zero suggest prefetch.
68 * The prefetch occurs as a side-effect of calling OnOmniboxFocused() on
69 * the AutocompleteController object.
71 class ZeroSuggestPrefetcher
: public AutocompleteControllerDelegate
{
73 explicit ZeroSuggestPrefetcher(Profile
* profile
);
76 ~ZeroSuggestPrefetcher() override
;
79 // AutocompleteControllerDelegate:
80 void OnResultChanged(bool default_match_changed
) override
;
82 scoped_ptr
<AutocompleteController
> controller_
;
83 base::OneShotTimer
<ZeroSuggestPrefetcher
> expire_timer_
;
86 ZeroSuggestPrefetcher::ZeroSuggestPrefetcher(Profile
* profile
)
87 : controller_(new AutocompleteController(
88 make_scoped_ptr(new ChromeAutocompleteProviderClient(profile
)),
90 AutocompleteProvider::TYPE_ZERO_SUGGEST
)) {
91 // Creating an arbitrary fake_request_source to avoid passing in an invalid
92 // AutocompleteInput object.
93 base::string16
fake_request_source(base::ASCIIToUTF16(
94 "http://www.foobarbazblah.com"));
95 controller_
->Start(AutocompleteInput(
96 fake_request_source
, base::string16::npos
, std::string(),
97 GURL(fake_request_source
), OmniboxEventProto::INVALID_SPEC
, false, false,
98 true, true, true, ChromeAutocompleteSchemeClassifier(profile
)));
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 make_scoped_ptr(new ChromeAutocompleteProviderClient(profile
)),
125 kAndroidAutocompleteProviders
)),
126 inside_synchronous_start_(false),
130 void AutocompleteControllerAndroid::Start(JNIEnv
* env
,
134 jstring j_desired_tld
,
135 jstring j_current_url
,
136 bool prevent_inline_autocomplete
,
138 bool allow_exact_keyword_match
,
139 bool want_asynchronous_matches
) {
140 if (!autocomplete_controller_
)
143 std::string desired_tld
;
145 if (j_current_url
!= NULL
)
146 current_url
= GURL(ConvertJavaStringToUTF16(env
, j_current_url
));
147 if (j_desired_tld
!= NULL
)
148 desired_tld
= base::android::ConvertJavaStringToUTF8(env
, j_desired_tld
);
149 base::string16 text
= ConvertJavaStringToUTF16(env
, j_text
);
150 OmniboxEventProto::PageClassification page_classification
=
151 OmniboxEventProto::OTHER
;
152 size_t cursor_pos
= j_cursor_pos
== -1 ? base::string16::npos
: j_cursor_pos
;
153 input_
= AutocompleteInput(text
, cursor_pos
, desired_tld
, current_url
,
154 page_classification
, prevent_inline_autocomplete
,
155 prefer_keyword
, allow_exact_keyword_match
,
156 want_asynchronous_matches
, false,
157 ChromeAutocompleteSchemeClassifier(profile_
));
158 autocomplete_controller_
->Start(input_
);
161 ScopedJavaLocalRef
<jobject
> AutocompleteControllerAndroid::Classify(
165 return GetTopSynchronousResult(env
, obj
, j_text
, true);
168 void AutocompleteControllerAndroid::OnOmniboxFocused(
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
, std::string(), current_url
,
189 ClassifyPage(current_url
, is_query_in_omnibox
, focused_from_fakebox
),
190 false, false, true, true, true,
191 ChromeAutocompleteSchemeClassifier(profile_
));
192 autocomplete_controller_
->Start(input_
);
195 void AutocompleteControllerAndroid::Stop(JNIEnv
* env
,
197 bool clear_results
) {
198 if (autocomplete_controller_
!= NULL
)
199 autocomplete_controller_
->Stop(clear_results
);
202 void AutocompleteControllerAndroid::ResetSession(JNIEnv
* env
, jobject obj
) {
203 if (autocomplete_controller_
!= NULL
)
204 autocomplete_controller_
->ResetSession();
207 void AutocompleteControllerAndroid::OnSuggestionSelected(
211 jstring j_current_url
,
212 jboolean is_query_in_omnibox
,
213 jboolean focused_from_fakebox
,
214 jlong elapsed_time_since_first_modified
,
215 jobject j_web_contents
) {
216 base::string16 url
= ConvertJavaStringToUTF16(env
, j_current_url
);
217 const GURL current_url
= GURL(url
);
218 OmniboxEventProto::PageClassification current_page_classification
=
219 ClassifyPage(current_url
, is_query_in_omnibox
, focused_from_fakebox
);
220 const base::TimeTicks
& now(base::TimeTicks::Now());
221 content::WebContents
* web_contents
=
222 content::WebContents::FromJavaWebContents(j_web_contents
);
226 false, /* don't know */
231 SessionTabHelper::IdForTab(web_contents
),
232 current_page_classification
,
233 base::TimeDelta::FromMilliseconds(elapsed_time_since_first_modified
),
234 base::string16::npos
,
235 now
- autocomplete_controller_
->last_time_default_match_changed(),
236 autocomplete_controller_
->result());
237 autocomplete_controller_
->AddProvidersInfo(&log
.providers_info
);
239 OmniboxEventGlobalTracker::GetInstance()->OnURLOpened(&log
);
241 predictors::AutocompleteActionPredictorFactory::GetForProfile(profile_
)
242 ->OnOmniboxOpenedUrl(log
);
245 void AutocompleteControllerAndroid::DeleteSuggestion(JNIEnv
* env
,
247 int selected_index
) {
248 const AutocompleteResult
& result
= autocomplete_controller_
->result();
249 const AutocompleteMatch
& match
= result
.match_at(selected_index
);
250 if (match
.SupportsDeletion())
251 autocomplete_controller_
->DeleteMatch(match
);
254 ScopedJavaLocalRef
<jstring
> AutocompleteControllerAndroid::
255 UpdateMatchDestinationURLWithQueryFormulationTime(
259 jlong elapsed_time_since_input_change
) {
260 // In rare cases, we navigate to cached matches and the underlying result
261 // has already been cleared, in that case ignore the URL update.
262 if (autocomplete_controller_
->result().empty())
263 return ScopedJavaLocalRef
<jstring
>();
265 AutocompleteMatch
match(
266 autocomplete_controller_
->result().match_at(selected_index
));
267 autocomplete_controller_
->UpdateMatchDestinationURLWithQueryFormulationTime(
268 base::TimeDelta::FromMilliseconds(elapsed_time_since_input_change
),
270 return ConvertUTF8ToJavaString(env
, match
.destination_url
.spec());
273 void AutocompleteControllerAndroid::Shutdown() {
274 autocomplete_controller_
.reset();
276 JNIEnv
* env
= AttachCurrentThread();
277 ScopedJavaLocalRef
<jobject
> java_bridge
=
278 weak_java_autocomplete_controller_android_
.get(env
);
279 if (java_bridge
.obj())
280 Java_AutocompleteController_notifyNativeDestroyed(env
, java_bridge
.obj());
282 weak_java_autocomplete_controller_android_
.reset();
286 AutocompleteControllerAndroid
*
287 AutocompleteControllerAndroid::Factory::GetForProfile(
288 Profile
* profile
, JNIEnv
* env
, jobject obj
) {
289 AutocompleteControllerAndroid
* bridge
=
290 static_cast<AutocompleteControllerAndroid
*>(
291 GetInstance()->GetServiceForBrowserContext(profile
, true));
292 bridge
->InitJNI(env
, obj
);
296 AutocompleteControllerAndroid::Factory
*
297 AutocompleteControllerAndroid::Factory::GetInstance() {
298 return Singleton
<AutocompleteControllerAndroid::Factory
>::get();
301 content::BrowserContext
*
302 AutocompleteControllerAndroid::Factory::GetBrowserContextToUse(
303 content::BrowserContext
* context
) const {
304 return chrome::GetBrowserContextOwnInstanceInIncognito(context
);
307 AutocompleteControllerAndroid::Factory::Factory()
308 : BrowserContextKeyedServiceFactory(
309 "AutocompleteControllerAndroid",
310 BrowserContextDependencyManager::GetInstance()) {
311 DependsOn(ShortcutsBackendFactory::GetInstance());
314 AutocompleteControllerAndroid::Factory::~Factory() {
317 KeyedService
* AutocompleteControllerAndroid::Factory::BuildServiceInstanceFor(
318 content::BrowserContext
* profile
) const {
319 return new AutocompleteControllerAndroid(static_cast<Profile
*>(profile
));
322 AutocompleteControllerAndroid::~AutocompleteControllerAndroid() {
325 void AutocompleteControllerAndroid::InitJNI(JNIEnv
* env
, jobject obj
) {
326 weak_java_autocomplete_controller_android_
=
327 JavaObjectWeakGlobalRef(env
, obj
);
330 void AutocompleteControllerAndroid::OnResultChanged(
331 bool default_match_changed
) {
332 if (!autocomplete_controller_
)
335 const AutocompleteResult
& result
= autocomplete_controller_
->result();
336 const AutocompleteResult::const_iterator
default_match(
337 result
.default_match());
338 if ((default_match
!= result
.end()) && default_match_changed
&&
339 search::IsInstantExtendedAPIEnabled() &&
340 search::ShouldPrefetchSearchResults()) {
341 InstantSuggestion prefetch_suggestion
;
342 // If the default match should be prefetched, do that.
343 if (SearchProvider::ShouldPrefetch(*default_match
)) {
344 prefetch_suggestion
.text
= default_match
->contents
;
345 prefetch_suggestion
.metadata
=
346 SearchProvider::GetSuggestMetadata(*default_match
);
348 // Send the prefetch suggestion unconditionally to the Instant search base
349 // page. If there is no suggestion to prefetch, we need to send a blank
350 // query to clear the prefetched results.
351 InstantSearchPrerenderer
* prerenderer
=
352 InstantSearchPrerenderer::GetForProfile(profile_
);
354 prerenderer
->Prerender(prefetch_suggestion
);
356 if (!inside_synchronous_start_
)
357 NotifySuggestionsReceived(autocomplete_controller_
->result());
360 void AutocompleteControllerAndroid::NotifySuggestionsReceived(
361 const AutocompleteResult
& autocomplete_result
) {
362 JNIEnv
* env
= AttachCurrentThread();
363 ScopedJavaLocalRef
<jobject
> java_bridge
=
364 weak_java_autocomplete_controller_android_
.get(env
);
365 if (!java_bridge
.obj())
368 ScopedJavaLocalRef
<jobject
> suggestion_list_obj
=
369 Java_AutocompleteController_createOmniboxSuggestionList(
370 env
, autocomplete_result
.size());
371 for (size_t i
= 0; i
< autocomplete_result
.size(); ++i
) {
372 ScopedJavaLocalRef
<jobject
> j_omnibox_suggestion
=
373 BuildOmniboxSuggestion(env
, autocomplete_result
.match_at(i
));
374 Java_AutocompleteController_addOmniboxSuggestionToList(
375 env
, suggestion_list_obj
.obj(), j_omnibox_suggestion
.obj());
378 // Get the inline-autocomplete text.
379 const AutocompleteResult::const_iterator
default_match(
380 autocomplete_result
.default_match());
381 base::string16 inline_autocomplete_text
;
382 if (default_match
!= autocomplete_result
.end()) {
383 inline_autocomplete_text
= default_match
->inline_autocompletion
;
385 ScopedJavaLocalRef
<jstring
> inline_text
=
386 ConvertUTF16ToJavaString(env
, inline_autocomplete_text
);
387 jlong j_autocomplete_result
=
388 reinterpret_cast<intptr_t>(&(autocomplete_result
));
389 Java_AutocompleteController_onSuggestionsReceived(env
,
391 suggestion_list_obj
.obj(),
393 j_autocomplete_result
);
396 OmniboxEventProto::PageClassification
397 AutocompleteControllerAndroid::ClassifyPage(const GURL
& gurl
,
398 bool is_query_in_omnibox
,
399 bool focused_from_fakebox
) const {
400 if (!gurl
.is_valid())
401 return OmniboxEventProto::INVALID_SPEC
;
403 const std::string
& url
= gurl
.spec();
405 if (gurl
.SchemeIs(content::kChromeUIScheme
) &&
406 gurl
.host() == chrome::kChromeUINewTabHost
) {
407 return OmniboxEventProto::NTP
;
410 if (url
== chrome::kChromeUINativeNewTabURL
) {
411 return focused_from_fakebox
?
412 OmniboxEventProto::INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS
:
413 OmniboxEventProto::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS
;
416 if (url
== url::kAboutBlankURL
)
417 return OmniboxEventProto::BLANK
;
419 if (url
== profile_
->GetPrefs()->GetString(prefs::kHomePage
))
420 return OmniboxEventProto::HOME_PAGE
;
422 if (is_query_in_omnibox
)
423 return OmniboxEventProto::SEARCH_RESULT_PAGE_DOING_SEARCH_TERM_REPLACEMENT
;
425 bool is_search_url
= TemplateURLServiceFactory::GetForProfile(profile_
)->
426 IsSearchResultsPageFromDefaultSearchProvider(gurl
);
428 return OmniboxEventProto::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT
;
430 return OmniboxEventProto::OTHER
;
433 ScopedJavaLocalRef
<jobject
>
434 AutocompleteControllerAndroid::BuildOmniboxSuggestion(
436 const AutocompleteMatch
& match
) {
437 ScopedJavaLocalRef
<jstring
> contents
=
438 ConvertUTF16ToJavaString(env
, match
.contents
);
439 ScopedJavaLocalRef
<jstring
> description
=
440 ConvertUTF16ToJavaString(env
, match
.description
);
441 ScopedJavaLocalRef
<jstring
> answer_contents
=
442 ConvertUTF16ToJavaString(env
, match
.answer_contents
);
443 ScopedJavaLocalRef
<jstring
> answer_type
=
444 ConvertUTF16ToJavaString(env
, match
.answer_type
);
445 ScopedJavaLocalRef
<jstring
> fill_into_edit
=
446 ConvertUTF16ToJavaString(env
, match
.fill_into_edit
);
447 ScopedJavaLocalRef
<jstring
> destination_url
=
448 ConvertUTF8ToJavaString(env
, match
.destination_url
.spec());
449 // Note that we are also removing 'www' host from formatted url.
450 ScopedJavaLocalRef
<jstring
> formatted_url
= ConvertUTF16ToJavaString(env
,
451 FormatURLUsingAcceptLanguages(match
.stripped_destination_url
));
452 BookmarkModel
* bookmark_model
= BookmarkModelFactory::GetForProfile(profile_
);
453 return Java_AutocompleteController_buildOmniboxSuggestion(
460 answer_contents
.obj(),
462 fill_into_edit
.obj(),
463 destination_url
.obj(),
465 bookmark_model
&& bookmark_model
->IsBookmarked(match
.destination_url
),
466 match
.SupportsDeletion());
469 base::string16
AutocompleteControllerAndroid::FormatURLUsingAcceptLanguages(
471 if (profile_
== NULL
)
472 return base::string16();
474 std::string
languages(
475 profile_
->GetPrefs()->GetString(prefs::kAcceptLanguages
));
477 return url_formatter::FormatUrl(
478 url
, languages
, url_formatter::kFormatUrlOmitAll
,
479 net::UnescapeRule::SPACES
, nullptr, nullptr, nullptr);
482 ScopedJavaLocalRef
<jobject
>
483 AutocompleteControllerAndroid::GetTopSynchronousResult(
487 bool prevent_inline_autocomplete
) {
488 if (!autocomplete_controller_
)
489 return ScopedJavaLocalRef
<jobject
>();
491 inside_synchronous_start_
= true;
498 prevent_inline_autocomplete
,
502 inside_synchronous_start_
= false;
503 DCHECK(autocomplete_controller_
->done());
504 const AutocompleteResult
& result
= autocomplete_controller_
->result();
506 return ScopedJavaLocalRef
<jobject
>();
508 return BuildOmniboxSuggestion(env
, *result
.begin());
511 static jlong
Init(JNIEnv
* env
, jobject obj
, jobject jprofile
) {
512 Profile
* profile
= ProfileAndroid::FromProfileAndroid(jprofile
);
516 AutocompleteControllerAndroid
* native_bridge
=
517 AutocompleteControllerAndroid::Factory::GetForProfile(profile
, env
, obj
);
518 return reinterpret_cast<intptr_t>(native_bridge
);
521 static ScopedJavaLocalRef
<jstring
> QualifyPartialURLQuery(JNIEnv
* env
,
524 Profile
* profile
= ProfileManager::GetActiveUserProfile();
526 return ScopedJavaLocalRef
<jstring
>();
527 AutocompleteMatch match
;
528 base::string16
query_string(ConvertJavaStringToUTF16(env
, jquery
));
529 AutocompleteClassifierFactory::GetForProfile(profile
)->Classify(
533 OmniboxEventProto::INVALID_SPEC
,
536 if (!match
.destination_url
.is_valid())
537 return ScopedJavaLocalRef
<jstring
>();
539 // Only return a URL if the match is a URL type.
540 if (match
.type
!= AutocompleteMatchType::URL_WHAT_YOU_TYPED
&&
541 match
.type
!= AutocompleteMatchType::HISTORY_URL
&&
542 match
.type
!= AutocompleteMatchType::NAVSUGGEST
)
543 return ScopedJavaLocalRef
<jstring
>();
545 // As we are returning to Java, it is fine to call Release().
546 return ConvertUTF8ToJavaString(env
, match
.destination_url
.spec());
549 static void PrefetchZeroSuggestResults(JNIEnv
* env
, jclass clazz
) {
550 Profile
* profile
= ProfileManager::GetActiveUserProfile();
554 if (!OmniboxFieldTrial::InZeroSuggestPersonalizedFieldTrial())
557 // ZeroSuggestPrefetcher deletes itself after it's done prefetching.
558 new ZeroSuggestPrefetcher(profile
);
561 // Register native methods
562 bool RegisterAutocompleteControllerAndroid(JNIEnv
* env
) {
563 return RegisterNativesImpl(env
);