| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/search_engines/android/template_url_service_android.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <string> |
| #include <vector> |
| |
| #include "base/android/jni_array.h" |
| #include "base/android/jni_string.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/format_macros.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_ostream_operators.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "components/google/core/common/google_util.h" |
| #include "components/search_engines/android/template_url_android.h" |
| #include "components/search_engines/search_engine_choice/search_engine_choice_utils.h" |
| #include "components/search_engines/search_engines_switches.h" |
| #include "components/search_engines/search_terms_data.h" |
| #include "components/search_engines/template_url.h" |
| #include "components/search_engines/template_url_prepopulate_data.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "components/search_engines/util.h" |
| #include "components/search_provider_logos/switches.h" |
| #include "net/base/url_util.h" |
| #include "third_party/omnibox_proto/chrome_aim_entry_point.pb.h" |
| #include "url/android/gurl_android.h" |
| #include "url/gurl.h" |
| |
| // Must come after all headers that specialize FromJniType() / ToJniType(). |
| #include "components/search_engines/android/jni_headers/TemplateUrlService_jni.h" |
| |
| using base::android::JavaParamRef; |
| using base::android::ScopedJavaLocalRef; |
| |
| namespace { |
| TemplateURLData CreatePlayAPITemplateURLData( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jstring>& jname, |
| const base::android::JavaParamRef<jstring>& jkeyword, |
| const base::android::JavaParamRef<jstring>& jsearch_url, |
| const base::android::JavaParamRef<jstring>& jsuggest_url, |
| const base::android::JavaParamRef<jstring>& jfavicon_url, |
| const base::android::JavaParamRef<jstring>& jnew_tab_url, |
| const base::android::JavaParamRef<jstring>& jimage_url, |
| const base::android::JavaParamRef<jstring>& jimage_url_post_params, |
| const base::android::JavaParamRef<jstring>& jimage_translate_url, |
| const base::android::JavaParamRef<jstring>& |
| jimage_translate_source_language_param_key, |
| const base::android::JavaParamRef<jstring>& |
| jimage_translate_target_language_param_key) { |
| std::u16string keyword = |
| base::android::ConvertJavaStringToUTF16(env, jkeyword); |
| std::u16string name = base::android::ConvertJavaStringToUTF16(env, jname); |
| std::string search_url = base::android::ConvertJavaStringToUTF8(jsearch_url); |
| std::string suggest_url; |
| if (jsuggest_url) { |
| suggest_url = base::android::ConvertJavaStringToUTF8(jsuggest_url); |
| } |
| std::string favicon_url; |
| if (jfavicon_url) { |
| favicon_url = base::android::ConvertJavaStringToUTF8(jfavicon_url); |
| } |
| std::string new_tab_url; |
| if (jnew_tab_url) { |
| new_tab_url = base::android::ConvertJavaStringToUTF8(jnew_tab_url); |
| } |
| std::string image_url; |
| if (jimage_url) { |
| image_url = base::android::ConvertJavaStringToUTF8(jimage_url); |
| } |
| std::string image_url_post_params; |
| if (jimage_url_post_params) { |
| image_url_post_params = |
| base::android::ConvertJavaStringToUTF8(jimage_url_post_params); |
| } |
| std::string image_translate_url; |
| if (jimage_translate_url) { |
| image_translate_url = |
| base::android::ConvertJavaStringToUTF8(jimage_translate_url); |
| } |
| std::string image_translate_source_language_param_key; |
| if (jimage_translate_source_language_param_key) { |
| image_translate_source_language_param_key = |
| base::android::ConvertJavaStringToUTF8( |
| jimage_translate_source_language_param_key); |
| } |
| std::string image_translate_target_language_param_key; |
| if (jimage_translate_target_language_param_key) { |
| image_translate_target_language_param_key = |
| base::android::ConvertJavaStringToUTF8( |
| jimage_translate_target_language_param_key); |
| } |
| |
| return TemplateURLService::CreatePlayAPITemplateURLData( |
| keyword, name, search_url, suggest_url, favicon_url, new_tab_url, |
| image_url, image_url_post_params, image_translate_url, |
| image_translate_source_language_param_key, |
| image_translate_target_language_param_key |
| |
| ); |
| } |
| } // namespace |
| |
| TemplateUrlServiceAndroid::TemplateUrlServiceAndroid( |
| TemplateURLService* template_url_service) |
| : template_url_service_(template_url_service) { |
| template_url_subscription_ = template_url_service_->RegisterOnLoadedCallback( |
| base::BindOnce(&TemplateUrlServiceAndroid::OnTemplateURLServiceLoaded, |
| base::Unretained(this))); |
| template_url_service_->AddObserver(this); |
| } |
| |
| TemplateUrlServiceAndroid::~TemplateUrlServiceAndroid() { |
| if (java_ref_) { |
| Java_TemplateUrlService_clearNativePtr(jni_zero::AttachCurrentThread(), |
| java_ref_); |
| java_ref_.Reset(); |
| } |
| template_url_service_->RemoveObserver(this); |
| } |
| |
| ScopedJavaLocalRef<jobject> TemplateUrlServiceAndroid::GetJavaObject() { |
| JNIEnv* env = jni_zero::AttachCurrentThread(); |
| if (!java_ref_) { |
| java_ref_.Reset( |
| Java_TemplateUrlService_create(env, reinterpret_cast<intptr_t>(this))); |
| } |
| return ScopedJavaLocalRef<jobject>(java_ref_); |
| } |
| |
| void TemplateUrlServiceAndroid::Load(JNIEnv* env) { |
| template_url_service_->Load(); |
| } |
| |
| void TemplateUrlServiceAndroid::SetUserSelectedDefaultSearchProvider( |
| JNIEnv* env, |
| const JavaParamRef<jstring>& jkeyword, |
| jint choice_made_location) { |
| std::u16string keyword( |
| base::android::ConvertJavaStringToUTF16(env, jkeyword)); |
| TemplateURL* template_url = |
| template_url_service_->GetTemplateURLForKeyword(keyword); |
| template_url_service_->SetUserSelectedDefaultSearchProvider( |
| template_url, |
| static_cast<search_engines::ChoiceMadeLocation>(choice_made_location)); |
| } |
| |
| jboolean TemplateUrlServiceAndroid::IsLoaded(JNIEnv* env) const { |
| return template_url_service_->loaded(); |
| } |
| |
| jboolean TemplateUrlServiceAndroid::IsDefaultSearchManaged(JNIEnv* env) { |
| return template_url_service_->is_default_search_managed(); |
| } |
| |
| jboolean TemplateUrlServiceAndroid::IsSearchByImageAvailable(JNIEnv* env) { |
| const TemplateURL* default_search_provider = |
| template_url_service_->GetDefaultSearchProvider(); |
| return default_search_provider && |
| !default_search_provider->image_url().empty() && |
| default_search_provider->image_url_ref().IsValid( |
| template_url_service_->search_terms_data()); |
| } |
| |
| jboolean TemplateUrlServiceAndroid::DoesDefaultSearchEngineHaveLogo( |
| JNIEnv* env) { |
| // |kSearchProviderLogoURL| applies to all search engines (Google or |
| // third-party). |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| search_provider_logos::switches::kSearchProviderLogoURL)) { |
| return true; |
| } |
| |
| // Google always has a logo. |
| if (IsDefaultSearchEngineGoogle(env)) { |
| return true; |
| } |
| |
| // Third-party search engines can have a doodle specified via the command |
| // line, or a static logo or doodle from the TemplateURLService. |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| search_provider_logos::switches::kThirdPartyDoodleURL)) { |
| return true; |
| } |
| const TemplateURL* default_search_provider = |
| template_url_service_->GetDefaultSearchProvider(); |
| return default_search_provider && |
| (default_search_provider->doodle_url().is_valid() || |
| default_search_provider->logo_url().is_valid()); |
| } |
| |
| jboolean TemplateUrlServiceAndroid::IsDefaultSearchEngineGoogle(JNIEnv* env) { |
| return IsDefaultSearchEngineGoogle(); |
| } |
| |
| jboolean |
| TemplateUrlServiceAndroid::IsSearchResultsPageFromDefaultSearchProvider( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jobject>& jurl) { |
| GURL url = url::GURLAndroid::ToNativeGURL(env, jurl); |
| return template_url_service_->IsSearchResultsPageFromDefaultSearchProvider( |
| url); |
| } |
| |
| bool TemplateUrlServiceAndroid::IsDefaultSearchEngineGoogle() { |
| const TemplateURL* default_search_provider = |
| template_url_service_->GetDefaultSearchProvider(); |
| return default_search_provider && |
| default_search_provider->url_ref().HasGoogleBaseURLs( |
| template_url_service_->search_terms_data()); |
| } |
| |
| void TemplateUrlServiceAndroid::OnTemplateURLServiceLoaded() { |
| template_url_subscription_ = {}; |
| JNIEnv* env = jni_zero::AttachCurrentThread(); |
| if (!java_ref_) |
| return; |
| Java_TemplateUrlService_templateUrlServiceLoaded(env, java_ref_); |
| } |
| |
| void TemplateUrlServiceAndroid::OnTemplateURLServiceChanged() { |
| JNIEnv* env = jni_zero::AttachCurrentThread(); |
| if (!java_ref_) |
| return; |
| Java_TemplateUrlService_onTemplateURLServiceChanged(env, java_ref_); |
| } |
| |
| base::android::ScopedJavaLocalRef<jstring> |
| TemplateUrlServiceAndroid::GetUrlForSearchQuery( |
| JNIEnv* env, |
| const JavaParamRef<jstring>& jquery, |
| const JavaParamRef<jobjectArray>& jsearch_params) { |
| const TemplateURL* default_provider = |
| template_url_service_->GetDefaultSearchProvider(); |
| |
| std::u16string query(base::android::ConvertJavaStringToUTF16(env, jquery)); |
| |
| std::string url; |
| if (default_provider && |
| default_provider->url_ref().SupportsReplacement( |
| template_url_service_->search_terms_data()) && |
| !query.empty()) { |
| std::string additional_params; |
| if (jsearch_params) { |
| std::vector<std::string> params; |
| base::android::AppendJavaStringArrayToStringVector(env, jsearch_params, |
| ¶ms); |
| additional_params = base::JoinString(params, "&"); |
| } |
| TemplateURLRef::SearchTermsArgs args(query); |
| args.additional_query_params = std::move(additional_params); |
| url = default_provider->url_ref().ReplaceSearchTerms( |
| args, template_url_service_->search_terms_data()); |
| } |
| |
| return base::android::ConvertUTF8ToJavaString(env, url); |
| } |
| |
| base::android::ScopedJavaLocalRef<jstring> |
| TemplateUrlServiceAndroid::GetSearchQueryForUrl( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& jurl) { |
| const TemplateURL* default_provider = |
| template_url_service_->GetDefaultSearchProvider(); |
| |
| GURL url = url::GURLAndroid::ToNativeGURL(env, jurl); |
| |
| std::u16string query; |
| |
| if (default_provider && |
| default_provider->url_ref().SupportsReplacement( |
| template_url_service_->search_terms_data()) && |
| template_url_service_->IsSearchResultsPageFromDefaultSearchProvider( |
| url)) { |
| default_provider->ExtractSearchTermsFromURL( |
| url, template_url_service_->search_terms_data(), &query); |
| } |
| |
| return base::android::ConvertUTF16ToJavaString(env, query); |
| } |
| |
| base::android::ScopedJavaLocalRef<jobject> |
| TemplateUrlServiceAndroid::GetUrlForVoiceSearchQuery( |
| JNIEnv* env, |
| const JavaParamRef<jstring>& jquery) { |
| std::u16string query(base::android::ConvertJavaStringToUTF16(env, jquery)); |
| |
| if (!query.empty()) { |
| GURL gurl(GetDefaultSearchURLForSearchTerms(template_url_service_, query)); |
| if (IsDefaultSearchEngineGoogle()) |
| gurl = net::AppendQueryParameter(gurl, "inm", "vs"); |
| return url::GURLAndroid::FromNativeGURL(env, gurl); |
| } |
| |
| return url::GURLAndroid::EmptyGURL(env); |
| } |
| |
| base::android::ScopedJavaLocalRef<jobject> |
| TemplateUrlServiceAndroid::GetComposeplateUrl( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| if (!IsDefaultSearchEngineGoogle()) { |
| return nullptr; |
| } |
| |
| return url::GURLAndroid::FromNativeGURL( |
| env, GetUrlForAim(template_url_service_, |
| omnibox::ANDROID_CHROME_NTP_FAKE_OMNIBOX_ENTRY_POINT, |
| /*query_start_time=*/base::Time::Now())); |
| } |
| |
| base::android::ScopedJavaLocalRef<jobject> |
| TemplateUrlServiceAndroid::GetUrlForContextualSearchQuery( |
| JNIEnv* env, |
| const JavaParamRef<jstring>& jquery, |
| const JavaParamRef<jstring>& jalternate_term, |
| jboolean jshould_prefetch, |
| const JavaParamRef<jstring>& jprotocol_version) { |
| std::u16string query(base::android::ConvertJavaStringToUTF16(env, jquery)); |
| |
| if (!query.empty()) { |
| GURL gurl(GetDefaultSearchURLForSearchTerms(template_url_service_, query)); |
| if (IsDefaultSearchEngineGoogle()) { |
| std::string protocol_version( |
| base::android::ConvertJavaStringToUTF8(env, jprotocol_version)); |
| gurl = net::AppendQueryParameter(gurl, "ctxs", protocol_version); |
| if (jshould_prefetch) { |
| // Indicate that the search page is being prefetched. |
| gurl = net::AppendQueryParameter(gurl, "pf", "c"); |
| } |
| |
| if (jalternate_term) { |
| std::string alternate_term( |
| base::android::ConvertJavaStringToUTF8(env, jalternate_term)); |
| if (!alternate_term.empty()) { |
| gurl = net::AppendQueryParameter(gurl, "ctxsl_alternate_term", |
| alternate_term); |
| } |
| } |
| } |
| return url::GURLAndroid::FromNativeGURL(env, gurl); |
| } |
| |
| return url::GURLAndroid::EmptyGURL(env); |
| } |
| |
| base::android::ScopedJavaLocalRef<jstring> |
| TemplateUrlServiceAndroid::GetSearchEngineUrlFromTemplateUrl( |
| JNIEnv* env, |
| const JavaParamRef<jstring>& jkeyword) { |
| std::u16string keyword = |
| base::android::ConvertJavaStringToUTF16(env, jkeyword); |
| TemplateURL* template_url = |
| template_url_service_->GetTemplateURLForKeyword(keyword); |
| if (!template_url) |
| return base::android::ScopedJavaLocalRef<jstring>::Adopt(env, nullptr); |
| std::string url(template_url->url_ref().ReplaceSearchTerms( |
| TemplateURLRef::SearchTermsArgs(u"query"), |
| template_url_service_->search_terms_data())); |
| return base::android::ConvertUTF8ToJavaString(env, url); |
| } |
| |
| int TemplateUrlServiceAndroid::GetSearchEngineTypeFromTemplateUrl( |
| JNIEnv* env, |
| const JavaParamRef<jstring>& jkeyword) { |
| std::u16string keyword = |
| base::android::ConvertJavaStringToUTF16(env, jkeyword); |
| TemplateURL* template_url = |
| template_url_service_->GetTemplateURLForKeyword(keyword); |
| if (!template_url) |
| return -1; |
| const SearchTermsData& search_terms_data = |
| template_url_service_->search_terms_data(); |
| return template_url->GetEngineType(search_terms_data); |
| } |
| |
| jboolean TemplateUrlServiceAndroid::SetPlayAPISearchEngine( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jstring>& jname, |
| const base::android::JavaParamRef<jstring>& jkeyword, |
| const base::android::JavaParamRef<jstring>& jsearch_url, |
| const base::android::JavaParamRef<jstring>& jsuggest_url, |
| const base::android::JavaParamRef<jstring>& jfavicon_url, |
| const base::android::JavaParamRef<jstring>& jnew_tab_url, |
| const base::android::JavaParamRef<jstring>& jimage_url, |
| const base::android::JavaParamRef<jstring>& jimage_url_post_params, |
| const base::android::JavaParamRef<jstring>& jimage_translate_url, |
| const base::android::JavaParamRef<jstring>& |
| jimage_translate_source_language_param_key, |
| const base::android::JavaParamRef<jstring>& |
| jimage_translate_target_language_param_key) { |
| // The function is scheduled to run only when the service is loaded, see |
| // `TemplateUrlService#runWhenLoaded()`. |
| CHECK(template_url_service_->loaded()); |
| |
| // Check if there is already a search engine created by a regulatory program. |
| TemplateURLService::TemplateURLVector template_urls = |
| template_url_service_->GetTemplateURLs(); |
| TemplateURL* regulatory_api_turl = nullptr; |
| auto found = std::ranges::find_if(template_urls, |
| &TemplateURL::CreatedByRegulatoryProgram); |
| |
| if (found != template_urls.cend()) { |
| // Migrate old Play API database entries that were incorrectly marked as |
| // safe_for_autoreplace() before M89. |
| regulatory_api_turl = *found; |
| if (regulatory_api_turl->safe_for_autoreplace()) { |
| template_url_service_->ResetTemplateURL( |
| regulatory_api_turl, regulatory_api_turl->short_name(), |
| regulatory_api_turl->keyword(), regulatory_api_turl->url()); |
| } |
| } |
| |
| TemplateURLData new_play_api_turl_data = CreatePlayAPITemplateURLData( |
| env, jname, jkeyword, jsearch_url, jsuggest_url, jfavicon_url, |
| jnew_tab_url, jimage_url, jimage_url_post_params, jimage_translate_url, |
| jimage_translate_source_language_param_key, |
| jimage_translate_target_language_param_key); |
| |
| return template_url_service_->ResetPlayAPISearchEngine( |
| new_play_api_turl_data); |
| } |
| |
| base::android::ScopedJavaLocalRef<jstring> |
| TemplateUrlServiceAndroid::AddSearchEngineForTesting( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jstring>& jkeyword, |
| jint age_in_days) { |
| TemplateURLData data; |
| std::u16string keyword = |
| base::android::ConvertJavaStringToUTF16(env, jkeyword); |
| data.SetShortName(keyword); |
| data.SetKeyword(keyword); |
| data.SetURL("https://testurl.com/?searchstuff={searchTerms}"); |
| data.favicon_url = GURL("http://favicon.url"); |
| data.safe_for_autoreplace = true; |
| data.input_encodings.push_back("UTF-8"); |
| data.prepopulate_id = 0; |
| data.date_created = |
| base::Time::Now() - base::Days(static_cast<int>(age_in_days)); |
| data.last_modified = |
| base::Time::Now() - base::Days(static_cast<int>(age_in_days)); |
| data.last_visited = |
| base::Time::Now() - base::Days(static_cast<int>(age_in_days)); |
| TemplateURL* t_url = |
| template_url_service_->Add(std::make_unique<TemplateURL>(data)); |
| CHECK(t_url) << "Failed adding template url for: " << keyword; |
| return base::android::ConvertUTF16ToJavaString(env, t_url->data().keyword()); |
| } |
| |
| std::vector<raw_ptr<TemplateURL>> |
| TemplateUrlServiceAndroid::FilterUserSelectableTemplateUrls( |
| std::vector<raw_ptr<TemplateURL, VectorExperimental>> template_urls) { |
| std::vector<raw_ptr<TemplateURL>> result; |
| |
| // Clean up duplication between a Play API template URL and a corresponding |
| // prepopulated template URL. |
| auto regulatory_api_it = std::ranges::find_if( |
| template_urls, &TemplateURL::CreatedByRegulatoryProgram); |
| TemplateURL* regulatory_api_turl = |
| regulatory_api_it != template_urls.end() ? *regulatory_api_it : nullptr; |
| |
| for (TemplateURL* template_url : template_urls) { |
| // When Play API template URL supercedes the current template URL, skip it. |
| if (regulatory_api_turl && |
| regulatory_api_turl->keyword() == template_url->keyword() && |
| regulatory_api_turl->IsBetterThanConflictingEngine(template_url)) { |
| continue; |
| } |
| |
| // Do not include starter pack engines (@aimode, @tabs, ...) as these are |
| // not actual search engines. |
| if (template_url->starter_pack_id() != 0) { |
| continue; |
| } |
| |
| result.push_back(template_url); |
| } |
| |
| return result; |
| } |
| |
| void TemplateUrlServiceAndroid::GetTemplateUrls( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jobject>& template_url_list_obj) { |
| auto template_urls = FilterUserSelectableTemplateUrls( |
| template_url_service_->GetTemplateURLs()); |
| |
| for (TemplateURL* template_url : template_urls) { |
| Java_TemplateUrlService_addTemplateUrlToList( |
| env, template_url_list_obj, |
| CreateTemplateUrlAndroid(env, template_url)); |
| } |
| } |
| |
| base::android::ScopedJavaLocalRef<jobject> |
| TemplateUrlServiceAndroid::GetDefaultSearchEngine(JNIEnv* env) { |
| const TemplateURL* default_search_provider = |
| template_url_service_->GetDefaultSearchProvider(); |
| if (default_search_provider == nullptr) { |
| return base::android::ScopedJavaLocalRef<jobject>::Adopt(env, nullptr); |
| } |
| return CreateTemplateUrlAndroid(env, default_search_provider); |
| } |
| |
| base::android::ScopedJavaLocalRef<jobjectArray> |
| TemplateUrlServiceAndroid::GetImageUrlAndPostContent(JNIEnv* env) { |
| const TemplateURL* template_url = |
| template_url_service_->GetDefaultSearchProvider(); |
| |
| TemplateURLRef::PostContent post_content; |
| GURL result(template_url->image_url_ref().ReplaceSearchTerms( |
| TemplateURLRef::SearchTermsArgs(u""), |
| template_url_service_->search_terms_data(), &post_content)); |
| |
| std::vector<std::string> output; |
| output.push_back(result.spec()); |
| output.push_back(post_content.first); |
| return base::android::ToJavaArrayOfStrings(env, output); |
| } |