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