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 } 567