Index: chrome/browser/android/omnibox/autocomplete_controller_android.cc |
diff --git a/chrome/browser/android/omnibox/autocomplete_controller_android.cc b/chrome/browser/android/omnibox/autocomplete_controller_android.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..969341e49368f7b720d25ccb85263879b12c4763 |
--- /dev/null |
+++ b/chrome/browser/android/omnibox/autocomplete_controller_android.cc |
@@ -0,0 +1,502 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "chrome/browser/android/omnibox/autocomplete_controller_android.h" |
+ |
+#include "base/android/jni_android.h" |
+#include "base/android/jni_string.h" |
+#include "base/prefs/pref_service.h" |
+#include "base/strings/string16.h" |
+#include "base/strings/utf_string_conversions.h" |
+#include "base/time/time.h" |
+#include "base/timer/timer.h" |
+#include "chrome/browser/autocomplete/autocomplete_classifier.h" |
+#include "chrome/browser/autocomplete/autocomplete_classifier_factory.h" |
+#include "chrome/browser/autocomplete/autocomplete_controller.h" |
+#include "chrome/browser/autocomplete/autocomplete_input.h" |
+#include "chrome/browser/autocomplete/autocomplete_match.h" |
+#include "chrome/browser/autocomplete/shortcuts_backend_factory.h" |
+#include "chrome/browser/browser_process.h" |
+#include "chrome/browser/chrome_notification_types.h" |
+#include "chrome/browser/omnibox/omnibox_field_trial.h" |
+#include "chrome/browser/omnibox/omnibox_log.h" |
+#include "chrome/browser/profiles/incognito_helpers.h" |
+#include "chrome/browser/profiles/profile_android.h" |
+#include "chrome/browser/profiles/profile_manager.h" |
+#include "chrome/browser/search_engines/template_url_service.h" |
+#include "chrome/browser/search_engines/template_url_service_factory.h" |
+#include "chrome/browser/sessions/session_id.h" |
+#include "chrome/browser/ui/toolbar/toolbar_model.h" |
+#include "chrome/common/autocomplete_match_type.h" |
+#include "chrome/common/pref_names.h" |
+#include "chrome/common/url_constants.h" |
+#include "components/keyed_service/content/browser_context_dependency_manager.h" |
+#include "content/public/browser/notification_details.h" |
+#include "content/public/browser/notification_service.h" |
+#include "content/public/browser/notification_source.h" |
+#include "content/public/browser/web_contents.h" |
+#include "content/public/common/url_constants.h" |
+#include "jni/AutocompleteController_jni.h" |
+#include "net/base/escape.h" |
+#include "net/base/net_util.h" |
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
+#include "ui/base/resource/resource_bundle.h" |
+ |
+using base::android::AttachCurrentThread; |
+using base::android::ConvertJavaStringToUTF16; |
+using base::android::ConvertUTF8ToJavaString; |
+using base::android::ConvertUTF16ToJavaString; |
+ |
+namespace { |
+ |
+const int kAndroidAutocompleteProviders = |
+ AutocompleteClassifier::kDefaultOmniboxProviders; |
+ |
+/** |
+ * A prefetcher class responsible for triggering zero suggest prefetch. |
+ * The prefetch occurs as a side-effect of calling StartZeroSuggest() on |
+ * the AutocompleteController object. |
+ */ |
+class ZeroSuggestPrefetcher : public AutocompleteControllerDelegate { |
+ public: |
+ explicit ZeroSuggestPrefetcher(Profile* profile); |
+ |
+ private: |
+ virtual ~ZeroSuggestPrefetcher(); |
+ void SelfDestruct(); |
+ |
+ // AutocompleteControllerDelegate: |
+ virtual void OnResultChanged(bool default_match_changed) OVERRIDE; |
+ |
+ scoped_ptr<AutocompleteController> controller_; |
+ base::OneShotTimer<ZeroSuggestPrefetcher> expire_timer_; |
+}; |
+ |
+ZeroSuggestPrefetcher::ZeroSuggestPrefetcher(Profile* profile) : controller_( |
+ new AutocompleteController(profile, this, |
+ AutocompleteProvider::TYPE_ZERO_SUGGEST)) { |
+ // Creating an arbitrary fake_request_source to avoid passing in an invalid |
+ // AutocompleteInput object. |
+ base::string16 fake_request_source(base::ASCIIToUTF16( |
+ "http://www.foobarbazblah.com")); |
+ controller_->StartZeroSuggest(AutocompleteInput( |
+ fake_request_source, |
+ base::string16::npos, |
+ base::string16(), |
+ GURL(fake_request_source), |
+ AutocompleteInput::INVALID_SPEC, |
+ false, |
+ false, |
+ true, |
+ true)); |
+ // Delete ourselves after 10s. This is enough time to cache results or |
+ // give up if the results haven't been received. |
+ expire_timer_.Start(FROM_HERE, |
+ base::TimeDelta::FromMilliseconds(10000), |
+ this, &ZeroSuggestPrefetcher::SelfDestruct); |
+} |
+ |
+ZeroSuggestPrefetcher::~ZeroSuggestPrefetcher() { |
+} |
+ |
+void ZeroSuggestPrefetcher::SelfDestruct() { |
+ delete this; |
+} |
+ |
+void ZeroSuggestPrefetcher::OnResultChanged(bool default_match_changed) { |
+ // Nothing to do here, the results have been cached. |
+ // We don't want to trigger deletion here because this is being called by the |
+ // AutocompleteController object. |
+} |
+ |
+} // namespace |
+ |
+AutocompleteControllerAndroid::AutocompleteControllerAndroid(Profile* profile) |
+ : autocomplete_controller_(new AutocompleteController( |
+ profile, this, kAndroidAutocompleteProviders)), |
+ inside_classify_(false), |
+ profile_(profile) { |
+} |
+ |
+void AutocompleteControllerAndroid::Start(JNIEnv* env, |
+ jobject obj, |
+ jstring j_text, |
+ jstring j_desired_tld, |
+ jstring j_current_url, |
+ bool prevent_inline_autocomplete, |
+ bool prefer_keyword, |
+ bool allow_exact_keyword_match, |
+ bool want_asynchronous_matches) { |
+ if (!autocomplete_controller_) |
+ return; |
+ |
+ base::string16 desired_tld; |
+ GURL current_url; |
+ if (j_current_url != NULL) |
+ current_url = GURL(ConvertJavaStringToUTF16(env, j_current_url)); |
+ if (j_desired_tld != NULL) |
+ desired_tld = ConvertJavaStringToUTF16(env, j_desired_tld); |
+ base::string16 text = ConvertJavaStringToUTF16(env, j_text); |
+ AutocompleteInput::PageClassification page_classification = |
+ AutocompleteInput::OTHER; |
+ input_ = AutocompleteInput(text, |
+ base::string16::npos, |
+ desired_tld, |
+ current_url, |
+ page_classification, |
+ prevent_inline_autocomplete, |
+ prefer_keyword, |
+ allow_exact_keyword_match, |
+ want_asynchronous_matches); |
+ autocomplete_controller_->Start(input_); |
+} |
+ |
+ScopedJavaLocalRef<jobject> AutocompleteControllerAndroid::Classify( |
+ JNIEnv* env, |
+ jobject obj, |
+ jstring j_text) { |
+ if (!autocomplete_controller_) |
+ return ScopedJavaLocalRef<jobject>(); |
+ |
+ inside_classify_ = true; |
+ Start(env, obj, j_text, NULL, NULL, true, false, false, false); |
+ inside_classify_ = false; |
+ DCHECK(autocomplete_controller_->done()); |
+ const AutocompleteResult& result = autocomplete_controller_->result(); |
+ if (result.empty()) |
+ return ScopedJavaLocalRef<jobject>(); |
+ |
+ return BuildOmniboxSuggestion(env, *result.begin()); |
+} |
+ |
+void AutocompleteControllerAndroid::StartZeroSuggest( |
+ JNIEnv* env, |
+ jobject obj, |
+ jstring j_omnibox_text, |
+ jstring j_current_url, |
+ jboolean is_query_in_omnibox, |
+ jboolean focused_from_fakebox) { |
+ if (!autocomplete_controller_) |
+ return; |
+ |
+ base::string16 url = ConvertJavaStringToUTF16(env, j_current_url); |
+ const GURL current_url = GURL(url); |
+ base::string16 omnibox_text = ConvertJavaStringToUTF16(env, j_omnibox_text); |
+ |
+ // If omnibox text is empty, set it to the current URL for the purposes of |
+ // populating the verbatim match. |
+ if (omnibox_text.empty()) |
+ omnibox_text = url; |
+ |
+ input_ = AutocompleteInput( |
+ omnibox_text, base::string16::npos, base::string16(), current_url, |
+ ClassifyPage(current_url, is_query_in_omnibox, focused_from_fakebox), |
+ false, false, true, true); |
+ autocomplete_controller_->StartZeroSuggest(input_); |
+} |
+ |
+void AutocompleteControllerAndroid::Stop(JNIEnv* env, |
+ jobject obj, |
+ bool clear_results) { |
+ if (autocomplete_controller_ != NULL) |
+ autocomplete_controller_->Stop(clear_results); |
+} |
+ |
+void AutocompleteControllerAndroid::ResetSession(JNIEnv* env, jobject obj) { |
+ if (autocomplete_controller_ != NULL) |
+ autocomplete_controller_->ResetSession(); |
+} |
+ |
+void AutocompleteControllerAndroid::OnSuggestionSelected( |
+ JNIEnv* env, |
+ jobject obj, |
+ jint selected_index, |
+ jstring j_current_url, |
+ jboolean is_query_in_omnibox, |
+ jboolean focused_from_fakebox, |
+ jlong elapsed_time_since_first_modified, |
+ jobject j_web_contents) { |
+ base::string16 url = ConvertJavaStringToUTF16(env, j_current_url); |
+ const GURL current_url = GURL(url); |
+ AutocompleteInput::PageClassification current_page_classification = |
+ ClassifyPage(current_url, is_query_in_omnibox, focused_from_fakebox); |
+ const base::TimeTicks& now(base::TimeTicks::Now()); |
+ content::WebContents* web_contents = |
+ content::WebContents::FromJavaWebContents(j_web_contents); |
+ |
+ OmniboxLog log( |
+ input_.text(), |
+ false, /* don't know */ |
+ input_.type(), |
+ true, |
+ selected_index, |
+ false, |
+ SessionID::IdForTab(web_contents), |
+ current_page_classification, |
+ base::TimeDelta::FromMilliseconds(elapsed_time_since_first_modified), |
+ base::string16::npos, |
+ now - autocomplete_controller_->last_time_default_match_changed(), |
+ autocomplete_controller_->result()); |
+ autocomplete_controller_->AddProvidersInfo(&log.providers_info); |
+ |
+ content::NotificationService::current()->Notify( |
+ chrome::NOTIFICATION_OMNIBOX_OPENED_URL, |
+ content::Source<Profile>(profile_), |
+ content::Details<OmniboxLog>(&log)); |
+} |
+ |
+void AutocompleteControllerAndroid::DeleteSuggestion(JNIEnv* env, |
+ jobject obj, |
+ int selected_index) { |
+ const AutocompleteResult& result = autocomplete_controller_->result(); |
+ const AutocompleteMatch& match = result.match_at(selected_index); |
+ if (match.SupportsDeletion()) |
+ autocomplete_controller_->DeleteMatch(match); |
+} |
+ |
+ScopedJavaLocalRef<jstring> |
+AutocompleteControllerAndroid::UpdateMatchDestinationURL( |
+ JNIEnv* env, |
+ jobject obj, |
+ jint selected_index, |
+ jlong elapsed_time_since_input_change) { |
+ // In rare cases, we navigate to cached matches and the underlying result |
+ // has already been cleared, in that case ignore the URL update. |
+ if (autocomplete_controller_->result().empty()) |
+ return ScopedJavaLocalRef<jstring>(); |
+ |
+ AutocompleteMatch match( |
+ autocomplete_controller_->result().match_at(selected_index)); |
+ autocomplete_controller_->UpdateMatchDestinationURL( |
+ base::TimeDelta::FromMilliseconds(elapsed_time_since_input_change), |
+ &match); |
+ return ConvertUTF8ToJavaString(env, match.destination_url.spec()); |
+} |
+ |
+void AutocompleteControllerAndroid::Shutdown() { |
+ autocomplete_controller_.reset(); |
+ |
+ JNIEnv* env = AttachCurrentThread(); |
+ ScopedJavaLocalRef<jobject> java_bridge = |
+ weak_java_autocomplete_controller_android_.get(env); |
+ if (java_bridge.obj()) |
+ Java_AutocompleteController_notifyNativeDestroyed(env, java_bridge.obj()); |
+ |
+ weak_java_autocomplete_controller_android_.reset(); |
+} |
+ |
+// static |
+AutocompleteControllerAndroid* |
+AutocompleteControllerAndroid::Factory::GetForProfile( |
+ Profile* profile, JNIEnv* env, jobject obj) { |
+ AutocompleteControllerAndroid* bridge = |
+ static_cast<AutocompleteControllerAndroid*>( |
+ GetInstance()->GetServiceForBrowserContext(profile, true)); |
+ bridge->InitJNI(env, obj); |
+ return bridge; |
+} |
+ |
+AutocompleteControllerAndroid::Factory* |
+AutocompleteControllerAndroid::Factory::GetInstance() { |
+ return Singleton<AutocompleteControllerAndroid::Factory>::get(); |
+} |
+ |
+content::BrowserContext* |
+AutocompleteControllerAndroid::Factory::GetBrowserContextToUse( |
+ content::BrowserContext* context) const { |
+ return chrome::GetBrowserContextOwnInstanceInIncognito(context); |
+} |
+ |
+AutocompleteControllerAndroid::Factory::Factory() |
+ : BrowserContextKeyedServiceFactory( |
+ "AutocompleteControllerAndroid", |
+ BrowserContextDependencyManager::GetInstance()) { |
+ DependsOn(ShortcutsBackendFactory::GetInstance()); |
+} |
+ |
+AutocompleteControllerAndroid::Factory::~Factory() { |
+} |
+ |
+KeyedService* AutocompleteControllerAndroid::Factory::BuildServiceInstanceFor( |
+ content::BrowserContext* profile) const { |
+ return new AutocompleteControllerAndroid(static_cast<Profile*>(profile)); |
+} |
+ |
+AutocompleteControllerAndroid::~AutocompleteControllerAndroid() { |
+} |
+ |
+void AutocompleteControllerAndroid::InitJNI(JNIEnv* env, jobject obj) { |
+ weak_java_autocomplete_controller_android_ = |
+ JavaObjectWeakGlobalRef(env, obj); |
+} |
+ |
+void AutocompleteControllerAndroid::OnResultChanged( |
+ bool default_match_changed) { |
+ if (autocomplete_controller_.get() != NULL && !inside_classify_) |
+ NotifySuggestionsReceived(autocomplete_controller_->result()); |
+} |
+ |
+void AutocompleteControllerAndroid::NotifySuggestionsReceived( |
+ const AutocompleteResult& autocomplete_result) { |
+ JNIEnv* env = AttachCurrentThread(); |
+ ScopedJavaLocalRef<jobject> java_bridge = |
+ weak_java_autocomplete_controller_android_.get(env); |
+ if (!java_bridge.obj()) |
+ return; |
+ |
+ ScopedJavaLocalRef<jobject> suggestion_list_obj = |
+ Java_AutocompleteController_createOmniboxSuggestionList( |
+ env, autocomplete_result.size()); |
+ for (size_t i = 0; i < autocomplete_result.size(); ++i) { |
+ ScopedJavaLocalRef<jobject> j_omnibox_suggestion = |
+ BuildOmniboxSuggestion(env, autocomplete_result.match_at(i)); |
+ Java_AutocompleteController_addOmniboxSuggestionToList( |
+ env, suggestion_list_obj.obj(), j_omnibox_suggestion.obj()); |
+ } |
+ |
+ // Get the inline-autocomplete text. |
+ const AutocompleteResult::const_iterator default_match( |
+ autocomplete_result.default_match()); |
+ base::string16 inline_autocomplete_text; |
+ if (default_match != autocomplete_result.end()) { |
+ inline_autocomplete_text = default_match->inline_autocompletion; |
+ } |
+ ScopedJavaLocalRef<jstring> inline_text = |
+ ConvertUTF16ToJavaString(env, inline_autocomplete_text); |
+ jlong j_autocomplete_result = |
+ reinterpret_cast<intptr_t>(&(autocomplete_result)); |
+ Java_AutocompleteController_onSuggestionsReceived(env, |
+ java_bridge.obj(), |
+ suggestion_list_obj.obj(), |
+ inline_text.obj(), |
+ j_autocomplete_result); |
+} |
+ |
+ScopedJavaLocalRef<jobject> |
+AutocompleteControllerAndroid::BuildOmniboxSuggestion( |
+ JNIEnv* env, |
+ const AutocompleteMatch& match) { |
+ ScopedJavaLocalRef<jstring> contents = |
+ ConvertUTF16ToJavaString(env, match.contents); |
+ ScopedJavaLocalRef<jstring> description = |
+ ConvertUTF16ToJavaString(env, match.description); |
+ ScopedJavaLocalRef<jstring> fill_into_edit = |
+ ConvertUTF16ToJavaString(env, match.fill_into_edit); |
+ ScopedJavaLocalRef<jstring> destination_url = |
+ ConvertUTF8ToJavaString(env, match.destination_url.spec()); |
+ // Note that we are also removing 'www' host from formatted url. |
+ ScopedJavaLocalRef<jstring> formatted_url = ConvertUTF16ToJavaString(env, |
+ FormatURLUsingAcceptLanguages(match.stripped_destination_url)); |
+ return Java_AutocompleteController_buildOmniboxSuggestion( |
+ env, |
+ match.type, |
+ match.relevance, |
+ match.transition, |
+ contents.obj(), |
+ description.obj(), |
+ fill_into_edit.obj(), |
+ destination_url.obj(), |
+ formatted_url.obj(), |
+ match.starred, |
+ match.SupportsDeletion()); |
+} |
+ |
+base::string16 AutocompleteControllerAndroid::FormatURLUsingAcceptLanguages( |
+ GURL url) { |
+ if (profile_ == NULL) |
+ return base::string16(); |
+ |
+ std::string languages( |
+ profile_->GetPrefs()->GetString(prefs::kAcceptLanguages)); |
+ |
+ return net::FormatUrl(url, languages, net::kFormatUrlOmitAll, |
+ net::UnescapeRule::SPACES, NULL, NULL, NULL); |
+} |
+ |
+AutocompleteInput::PageClassification |
+AutocompleteControllerAndroid::ClassifyPage(const GURL& gurl, |
+ bool is_query_in_omnibox, |
+ bool focused_from_fakebox) const { |
+ if (!gurl.is_valid()) |
+ return AutocompleteInput::INVALID_SPEC; |
+ |
+ const std::string& url = gurl.spec(); |
+ |
+ if (gurl.SchemeIs(content::kChromeUIScheme) && |
+ gurl.host() == chrome::kChromeUINewTabHost) { |
+ return AutocompleteInput::NTP; |
+ } |
+ if (url == chrome::kChromeUINativeNewTabURL) { |
+ return focused_from_fakebox ? |
+ AutocompleteInput::INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS : |
+ AutocompleteInput::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS; |
+ } |
+ if (url == content::kAboutBlankURL) |
+ return AutocompleteInput::BLANK; |
+ |
+ if (url == profile_->GetPrefs()->GetString(prefs::kHomePage)) |
+ return AutocompleteInput::HOME_PAGE; |
+ if (is_query_in_omnibox) |
+ return AutocompleteInput::SEARCH_RESULT_PAGE_DOING_SEARCH_TERM_REPLACEMENT; |
+ |
+ bool is_search_url = TemplateURLServiceFactory::GetForProfile(profile_)-> |
+ IsSearchResultsPageFromDefaultSearchProvider(gurl); |
+ if (is_search_url) |
+ return AutocompleteInput::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT; |
+ return AutocompleteInput::OTHER; |
+} |
+ |
+static jlong Init(JNIEnv* env, jobject obj, jobject jprofile) { |
+ Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile); |
+ if (!profile) |
+ return 0; |
+ |
+ AutocompleteControllerAndroid* native_bridge = |
+ AutocompleteControllerAndroid::Factory::GetForProfile(profile, env, obj); |
+ return reinterpret_cast<intptr_t>(native_bridge); |
+} |
+ |
+static jstring QualifyPartialURLQuery( |
+ JNIEnv* env, jclass clazz, jstring jquery) { |
+ Profile* profile = ProfileManager::GetActiveUserProfile(); |
+ if (!profile) |
+ return NULL; |
+ AutocompleteMatch match; |
+ base::string16 query_string(ConvertJavaStringToUTF16(env, jquery)); |
+ AutocompleteClassifierFactory::GetForProfile(profile)->Classify( |
+ query_string, |
+ false, |
+ false, |
+ AutocompleteInput::INVALID_SPEC, |
+ &match, |
+ NULL); |
+ if (!match.destination_url.is_valid()) |
+ return NULL; |
+ |
+ // Only return a URL if the match is a URL type. |
+ if (match.type != AutocompleteMatchType::URL_WHAT_YOU_TYPED && |
+ match.type != AutocompleteMatchType::HISTORY_URL && |
+ match.type != AutocompleteMatchType::NAVSUGGEST) |
+ return NULL; |
+ |
+ // As we are returning to Java, it is fine to call Release(). |
+ return ConvertUTF8ToJavaString(env, match.destination_url.spec()).Release(); |
+} |
+ |
+static void PrefetchZeroSuggestResults(JNIEnv* env, jclass clazz) { |
+ Profile* profile = ProfileManager::GetActiveUserProfile(); |
+ if (!profile) |
+ return; |
+ |
+ if (!OmniboxFieldTrial::InZeroSuggestPersonalizedFieldTrial()) |
+ return; |
+ |
+ // ZeroSuggestPrefetcher deletes itself after it's done prefetching. |
+ new ZeroSuggestPrefetcher(profile); |
+} |
+ |
+// Register native methods |
+bool RegisterAutocompleteControllerAndroid(JNIEnv* env) { |
+ return RegisterNativesImpl(env); |
+} |