Rename GetIconID to GetIconId
[chromium-blink-merge.git] / chrome / browser / android / omnibox / autocomplete_controller_android.cc
blob9080238a39dc106fbd6e07641fd24e899f93a2a4
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;
61 namespace {
63 const int kAndroidAutocompleteProviders =
64 AutocompleteClassifier::kDefaultOmniboxProviders;
66 /**
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 {
72 public:
73 explicit ZeroSuggestPrefetcher(Profile* profile);
75 private:
76 ~ZeroSuggestPrefetcher() override;
77 void SelfDestruct();
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)),
89 this,
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() {
110 delete this;
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.
119 } // namespace
121 AutocompleteControllerAndroid::AutocompleteControllerAndroid(Profile* profile)
122 : autocomplete_controller_(new AutocompleteController(
123 make_scoped_ptr(new ChromeAutocompleteProviderClient(profile)),
124 this,
125 kAndroidAutocompleteProviders)),
126 inside_synchronous_start_(false),
127 profile_(profile) {
130 void AutocompleteControllerAndroid::Start(JNIEnv* env,
131 jobject obj,
132 jstring j_text,
133 jint j_cursor_pos,
134 jstring j_desired_tld,
135 jstring j_current_url,
136 bool prevent_inline_autocomplete,
137 bool prefer_keyword,
138 bool allow_exact_keyword_match,
139 bool want_asynchronous_matches) {
140 if (!autocomplete_controller_)
141 return;
143 std::string desired_tld;
144 GURL current_url;
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(
162 JNIEnv* env,
163 jobject obj,
164 jstring j_text) {
165 return GetTopSynchronousResult(env, obj, j_text, true);
168 void AutocompleteControllerAndroid::OnOmniboxFocused(
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;
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())
185 omnibox_text = url;
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,
196 jobject obj,
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(
208 JNIEnv* env,
209 jobject obj,
210 jint selected_index,
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);
224 OmniboxLog log(
225 input_.text(),
226 false, /* don't know */
227 input_.type(),
228 true,
229 selected_index,
230 false,
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,
246 jobject obj,
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(
256 JNIEnv* env,
257 jobject obj,
258 jint selected_index,
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),
269 &match);
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();
285 // static
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);
293 return bridge;
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_)
333 return;
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_);
353 if (prerenderer)
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())
366 return;
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,
390 java_bridge.obj(),
391 suggestion_list_obj.obj(),
392 inline_text.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);
427 if (is_search_url)
428 return OmniboxEventProto::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT;
430 return OmniboxEventProto::OTHER;
433 ScopedJavaLocalRef<jobject>
434 AutocompleteControllerAndroid::BuildOmniboxSuggestion(
435 JNIEnv* env,
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(
454 env,
455 match.type,
456 match.relevance,
457 match.transition,
458 contents.obj(),
459 description.obj(),
460 answer_contents.obj(),
461 answer_type.obj(),
462 fill_into_edit.obj(),
463 destination_url.obj(),
464 formatted_url.obj(),
465 bookmark_model && bookmark_model->IsBookmarked(match.destination_url),
466 match.SupportsDeletion());
469 base::string16 AutocompleteControllerAndroid::FormatURLUsingAcceptLanguages(
470 GURL url) {
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(
484 JNIEnv* env,
485 jobject obj,
486 jstring j_text,
487 bool prevent_inline_autocomplete) {
488 if (!autocomplete_controller_)
489 return ScopedJavaLocalRef<jobject>();
491 inside_synchronous_start_ = true;
492 Start(env,
493 obj,
494 j_text,
496 NULL,
497 NULL,
498 prevent_inline_autocomplete,
499 false,
500 false,
501 false);
502 inside_synchronous_start_ = false;
503 DCHECK(autocomplete_controller_->done());
504 const AutocompleteResult& result = autocomplete_controller_->result();
505 if (result.empty())
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);
513 if (!profile)
514 return 0;
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,
522 jclass clazz,
523 jstring jquery) {
524 Profile* profile = ProfileManager::GetActiveUserProfile();
525 if (!profile)
526 return ScopedJavaLocalRef<jstring>();
527 AutocompleteMatch match;
528 base::string16 query_string(ConvertJavaStringToUTF16(env, jquery));
529 AutocompleteClassifierFactory::GetForProfile(profile)->Classify(
530 query_string,
531 false,
532 false,
533 OmniboxEventProto::INVALID_SPEC,
534 &match,
535 NULL);
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();
551 if (!profile)
552 return;
554 if (!OmniboxFieldTrial::InZeroSuggestPersonalizedFieldTrial())
555 return;
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);