blob: a090b1d27ea75b13e2aa3d2b1e6d033960c79f29 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// 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 <jni.h>
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <string>
#include <vector>
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/check.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/singleton.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/android/autocomplete/tab_matcher_android.h"
#include "chrome/browser/android/omnibox/chrome_omnibox_navigation_observer_android.h"
#include "chrome/browser/android/tab_android.h"
#include "chrome/browser/autocomplete/chrome_autocomplete_provider_client.h"
#include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
#include "chrome/browser/autocomplete/shortcuts_backend_factory.h"
#include "chrome/browser/omnibox/autocomplete_controller_emitter_factory.h"
#include "chrome/browser/predictors/autocomplete_action_predictor.h"
#include "chrome/browser/predictors/autocomplete_action_predictor_factory.h"
#include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service.h"
#include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service_factory.h"
#include "chrome/browser/preloading/prerender/prerender_manager.h"
#include "chrome/browser/preloading/search_preload/search_preload_service.h"
#include "chrome/browser/preloading/search_preload/search_preload_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/common/webui_url_constants.h"
#include "components/browser_ui/util/android/url_constants.h"
#include "components/omnibox/browser/actions/omnibox_answer_action.h"
#include "components/omnibox/browser/autocomplete_classifier.h"
#include "components/omnibox/browser/autocomplete_controller_emitter.h"
#include "components/omnibox/browser/autocomplete_enums.h"
#include "components/omnibox/browser/autocomplete_grouper_sections.h"
#include "components/omnibox/browser/autocomplete_input.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_match_type.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/autocomplete_provider_client.h"
#include "components/omnibox/browser/autocomplete_result.h"
#include "components/omnibox/browser/history_fuzzy_provider.h"
#include "components/omnibox/browser/omnibox_event_global_tracker.h"
#include "components/omnibox/browser/omnibox_field_trial.h"
#include "components/omnibox/browser/omnibox_log.h"
#include "components/omnibox/browser/omnibox_popup_selection.h"
#include "components/omnibox/browser/page_classification_functions.h"
#include "components/omnibox/browser/suggestion_answer.h"
#include "components/omnibox/browser/voice_suggest_provider.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/open_from_clipboard/clipboard_recent_content.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_service.h"
#include "components/sessions/content/session_tab_helper.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/spare_render_process_host_manager.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "net/cookies/cookie_util.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"
#include "third_party/metrics_proto/omnibox_focus_type.pb.h"
#include "ui/base/page_transition_types.h"
#include "ui/base/window_open_disposition.h"
#include "url/android/gurl_android.h"
#include "url/gurl.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/browser/ui/android/omnibox/jni_headers/AutocompleteController_jni.h"
using base::android::AppendJavaStringArrayToStringVector;
using base::android::AttachCurrentThread;
using base::android::ConvertJavaStringToUTF16;
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertUTF16ToJavaString;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaFloatArrayToFloatVector;
using base::android::JavaParamRef;
using base::android::JavaRef;
using base::android::ScopedJavaLocalRef;
using metrics::OmniboxEventProto;
namespace {
void RecordClipboardMetrics(AutocompleteMatchType::Type match_type) {
if (match_type != AutocompleteMatchType::CLIPBOARD_URL &&
match_type != AutocompleteMatchType::CLIPBOARD_TEXT &&
match_type != AutocompleteMatchType::CLIPBOARD_IMAGE) {
return;
}
base::TimeDelta age =
ClipboardRecentContent::GetInstance()->GetClipboardContentAge();
UMA_HISTOGRAM_LONG_TIMES_100("MobileOmnibox.PressedClipboardSuggestionAge",
age);
if (match_type == AutocompleteMatchType::CLIPBOARD_URL) {
UMA_HISTOGRAM_LONG_TIMES_100(
"MobileOmnibox.PressedClipboardSuggestionAge.URL", age);
} else if (match_type == AutocompleteMatchType::CLIPBOARD_TEXT) {
UMA_HISTOGRAM_LONG_TIMES_100(
"MobileOmnibox.PressedClipboardSuggestionAge.TEXT", age);
} else if (match_type == AutocompleteMatchType::CLIPBOARD_IMAGE) {
UMA_HISTOGRAM_LONG_TIMES_100(
"MobileOmnibox.PressedClipboardSuggestionAge.IMAGE", age);
}
}
} // namespace
AutocompleteControllerAndroid::AutocompleteControllerAndroid(
Profile* profile,
std::unique_ptr<ChromeAutocompleteProviderClient> client,
bool is_low_memory_device)
: is_low_memory_device_{is_low_memory_device},
profile_{profile},
java_controller_{Java_AutocompleteController_Constructor(
AttachCurrentThread(),
reinterpret_cast<intptr_t>(this))},
autocomplete_controller_{std::make_unique<AutocompleteController>(
std::move(client),
AutocompleteClassifier::DefaultOmniboxProviders(
is_low_memory_device))} {
autocomplete_controller_->AddObserver(this);
AutocompleteControllerEmitter* emitter =
AutocompleteControllerEmitterFactory::GetForBrowserContext(profile_);
if (emitter) {
autocomplete_controller_->AddObserver(emitter);
}
}
void AutocompleteControllerAndroid::Start(JNIEnv* env,
const JavaRef<jstring>& j_text,
jint j_cursor_pos,
const JavaRef<jstring>& j_desired_tld,
const JavaRef<jstring>& j_current_url,
jint j_page_classification,
bool prevent_inline_autocomplete,
bool prefer_keyword,
bool allow_exact_keyword_match,
bool want_asynchronous_matches) {
autocomplete_controller_->result().DestroyJavaObject();
std::string desired_tld;
GURL current_url;
if (!j_current_url.is_null()) {
current_url = GURL(ConvertJavaStringToUTF16(env, j_current_url));
}
if (!j_desired_tld.is_null()) {
desired_tld = ConvertJavaStringToUTF8(env, j_desired_tld);
}
std::u16string text = ConvertJavaStringToUTF16(env, j_text);
size_t cursor_pos = j_cursor_pos == -1 ? std::u16string::npos : j_cursor_pos;
input_ = AutocompleteInput(
text, cursor_pos, desired_tld,
OmniboxEventProto::PageClassification(j_page_classification),
ChromeAutocompleteSchemeClassifier(profile_));
input_.set_current_url(current_url);
input_.set_prevent_inline_autocomplete(prevent_inline_autocomplete);
input_.set_prefer_keyword(prefer_keyword);
input_.set_allow_exact_keyword_match(allow_exact_keyword_match);
input_.set_omit_asynchronous_matches(!want_asynchronous_matches);
autocomplete_controller_->Start(input_);
}
void AutocompleteControllerAndroid::StartPrefetch(
JNIEnv* env,
const JavaRef<jstring>& j_current_url,
jint j_page_classification) {
auto page_classification =
OmniboxEventProto::PageClassification(j_page_classification);
GURL current_url;
std::u16string auto_complete_text;
if (!j_current_url.is_null()) {
current_url = GURL(ConvertJavaStringToUTF16(env, j_current_url));
// We will not assign text to autocomplete input when on NTP page.
auto_complete_text = omnibox::IsNTPPage(page_classification)
? u""
: ConvertJavaStringToUTF16(env, j_current_url);
}
AutocompleteInput input(auto_complete_text, page_classification,
ChromeAutocompleteSchemeClassifier(profile_));
input.set_current_url(current_url);
input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS);
autocomplete_controller_->StartPrefetch(input);
}
ScopedJavaLocalRef<jobject> AutocompleteControllerAndroid::Classify(
JNIEnv* env,
const JavaParamRef<jstring>& j_text) {
// The old AutocompleteResult is about to be invalidated.
autocomplete_controller_->result().DestroyJavaObject();
inside_synchronous_start_ = true;
Start(env, j_text, -1, nullptr, nullptr, true, false, false, false, false);
inside_synchronous_start_ = false;
DCHECK(autocomplete_controller_->done());
const AutocompleteResult& result = autocomplete_controller_->result();
if (result.empty()) {
return ScopedJavaLocalRef<jobject>();
}
return ScopedJavaLocalRef<jobject>(
result.begin()->GetOrCreateJavaObject(env));
}
void AutocompleteControllerAndroid::OnOmniboxFocused(
JNIEnv* env,
const JavaParamRef<jstring>& j_omnibox_text,
const JavaParamRef<jstring>& j_current_url,
jint j_page_classification,
const JavaParamRef<jstring>& j_current_title) {
using OFT = metrics::OmniboxFocusType;
// Prevents double triggering of zero suggest when OnOmniboxFocused is issued
// in quick succession (due to odd timing in the Android focus callbacks).
if (!autocomplete_controller_->done()) {
return;
}
std::u16string url = ConvertJavaStringToUTF16(env, j_current_url);
std::u16string current_title = ConvertJavaStringToUTF16(env, j_current_title);
const GURL current_url = GURL(url);
std::u16string 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() && !current_url.SchemeIs(content::kChromeUIScheme) &&
!current_url.SchemeIs(browser_ui::kChromeUINativeScheme)) {
omnibox_text = url;
}
auto page_class =
OmniboxEventProto::PageClassification(j_page_classification);
if (!omnibox::IsNTPPage(page_class)) {
omnibox_text.clear();
}
// Proactively start up a renderer, to reduce the time to display search
// results, especially if a Service Worker is used. This is done in a PostTask
// with a experiment-configured delay so that the CPU usage associated with
// starting a new renderer process does not impact the Omnibox initialization.
// Note that there's a small chance the renderer will be started after the
// next navigation if the delay is too long, but the spare renderer will
// probably get used anyways by a later navigation.
if (!profile_->IsOffTheRecord() &&
page_class != OmniboxEventProto::ANDROID_SEARCH_WIDGET &&
!omnibox::IsNTPPage(page_class)) {
int spare_renderer_delay_ms = omnibox::kOmniboxSpareRendererDelayMs.Get();
content::GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE,
base::BindOnce(&AutocompleteControllerAndroid::WarmUpRenderProcess,
weak_ptr_factory_.GetWeakPtr()),
base::Milliseconds(spare_renderer_delay_ms));
}
input_ = AutocompleteInput(omnibox_text, page_class,
ChromeAutocompleteSchemeClassifier(profile_));
input_.set_current_url(current_url);
input_.set_current_title(current_title);
input_.set_focus_type(OFT::INTERACTION_FOCUS);
autocomplete_controller_->Start(input_);
}
void AutocompleteControllerAndroid::Stop(JNIEnv* env, bool clear_results) {
autocomplete_controller_->Stop(clear_results
? AutocompleteStopReason::kClobbered
: AutocompleteStopReason::kInteraction);
}
void AutocompleteControllerAndroid::ResetSession(JNIEnv* env) {
autocomplete_controller_->ResetSession();
}
void AutocompleteControllerAndroid::OnSuggestionSelected(
JNIEnv* env,
uintptr_t match_ptr,
int suggestion_line,
const jint j_window_open_disposition,
const JavaParamRef<jstring>& j_current_url,
jint j_page_classification,
jlong elapsed_time_since_first_modified,
jint completed_length,
const JavaParamRef<jobject>& j_web_contents) {
std::u16string url = ConvertJavaStringToUTF16(env, j_current_url);
const GURL current_url = GURL(url);
const base::TimeTicks& now(base::TimeTicks::Now());
content::WebContents* web_contents =
content::WebContents::FromJavaWebContents(j_web_contents);
const auto& match = *reinterpret_cast<AutocompleteMatch*>(match_ptr);
omnibox::answer_data_parser::LogAnswerUsed(match.answer_type);
TemplateURLService* template_url_service =
TemplateURLServiceFactory::GetForProfile(profile_);
if (template_url_service &&
template_url_service->IsSearchResultsPageFromDefaultSearchProvider(
match.destination_url)) {
UMA_HISTOGRAM_BOOLEAN("Omnibox.Search.OffTheRecord",
profile_->IsOffTheRecord());
}
RecordClipboardMetrics(match.type);
HistoryFuzzyProvider::RecordOpenMatchMetrics(
autocomplete_controller_->result(), match);
// The following histogram should be recorded for both TYPED and pasted
// URLs, but should still exclude reloads.
if (ui::PageTransitionTypeIncludingQualifiersIs(match.transition,
ui::PAGE_TRANSITION_TYPED) ||
ui::PageTransitionTypeIncludingQualifiersIs(match.transition,
ui::PAGE_TRANSITION_LINK)) {
net::cookie_util::RecordCookiePortOmniboxHistograms(match.destination_url);
}
AutocompleteMatch::LogSearchEngineUsed(
match, TemplateURLServiceFactory::GetForProfile(profile_));
OmniboxLog log(
// For zero suggest, record an empty input string instead of the
// current URL.
input_.IsZeroSuggest() ? std::u16string() : input_.text(),
false, /* don't know */
input_.type(), false, /* not keyword mode */
OmniboxEventProto::INVALID, true, OmniboxPopupSelection(suggestion_line),
static_cast<WindowOpenDisposition>(j_window_open_disposition), false,
sessions::SessionTabHelper::IdForTab(web_contents),
OmniboxEventProto::PageClassification(j_page_classification),
base::Milliseconds(elapsed_time_since_first_modified), completed_length,
now - autocomplete_controller_->last_time_default_match_changed(),
autocomplete_controller_->result(), match.destination_url,
profile_->IsOffTheRecord(), input_.IsZeroSuggest(), match.session);
autocomplete_controller_->AddProviderAndTriggeringLogs(&log);
OmniboxEventGlobalTracker::GetInstance()->OnURLOpened(&log);
if (web_contents) {
if (auto* search_prefetch_service =
SearchPrefetchServiceFactory::GetForProfile(profile_)) {
search_prefetch_service->OnURLOpenedFromOmnibox(&log);
}
// Record the value if prerender for search suggestion was not started.
// Other values (kHitFinished, kUnused, kCancelled) are recorded in
// PrerenderManager.
auto* prerender_manager = PrerenderManager::FromWebContents(web_contents);
if (!prerender_manager ||
!prerender_manager->HasSearchResultPagePrerendered()) {
base::UmaHistogramEnumeration(
internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine,
PrerenderPredictionStatus::kNotStarted);
}
}
predictors::AutocompleteActionPredictorFactory::GetForProfile(profile_)
->OnOmniboxOpenedUrl(log);
}
jboolean AutocompleteControllerAndroid::OnSuggestionTouchDown(
JNIEnv* env,
uintptr_t match_ptr,
int match_index,
const base::android::JavaParamRef<jobject>& j_web_contents) {
const auto& match = *reinterpret_cast<AutocompleteMatch*>(match_ptr);
if (SearchPrefetchService* search_prefetch_service =
SearchPrefetchServiceFactory::GetForProfile(profile_)) {
return search_prefetch_service->OnNavigationLikely(
match_index, match, omnibox::mojom::NavigationPredictor::kTouchDown,
content::WebContents::FromJavaWebContents(j_web_contents));
}
if (SearchPreloadService* search_preload_service =
SearchPreloadServiceFactory::GetForProfile(profile_)) {
return search_preload_service->OnNavigationLikely(
match_index, match, omnibox::mojom::NavigationPredictor::kTouchDown,
content::WebContents::FromJavaWebContents(j_web_contents));
}
return false;
}
void AutocompleteControllerAndroid::DeleteMatch(JNIEnv* env,
uintptr_t match_ptr) {
const auto* match = reinterpret_cast<AutocompleteMatch*>(match_ptr);
if (match->SupportsDeletion()) {
autocomplete_controller_->DeleteMatch(*match);
}
}
void AutocompleteControllerAndroid::DeleteMatchElement(JNIEnv* env,
uintptr_t match_ptr,
jint element_index) {
const auto* match = reinterpret_cast<AutocompleteMatch*>(match_ptr);
if (match->SupportsDeletion()) {
autocomplete_controller_->DeleteMatchElement(*match, element_index);
}
}
ScopedJavaLocalRef<jobject> AutocompleteControllerAndroid::
UpdateMatchDestinationURLWithAdditionalSearchboxStats(
JNIEnv* env,
uintptr_t match_ptr,
jlong elapsed_time_since_input_change) {
auto* match = reinterpret_cast<AutocompleteMatch*>(match_ptr);
autocomplete_controller_
->UpdateMatchDestinationURLWithAdditionalSearchboxStats(
base::Milliseconds(elapsed_time_since_input_change), match);
return url::GURLAndroid::FromNativeGURL(env, match->destination_url);
}
base::android::ScopedJavaLocalRef<jobject>
AutocompleteControllerAndroid::GetAnswerActionDestinationURL(
JNIEnv* env,
uintptr_t match_ptr,
jlong elapsed_time_since_input_change,
uintptr_t answer_action_ptr) {
auto* match = reinterpret_cast<AutocompleteMatch*>(match_ptr);
auto* action = reinterpret_cast<OmniboxAnswerAction*>(answer_action_ptr);
TemplateURLService* template_url_service =
TemplateURLServiceFactory::GetForProfile(profile_);
if (action == nullptr || template_url_service == nullptr) {
return url::GURLAndroid::FromNativeGURL(env, GURL());
}
autocomplete_controller_->UpdateSearchTermsArgsWithAdditionalSearchboxStats(
base::Milliseconds(elapsed_time_since_input_change),
action->search_terms_args);
TemplateURL* template_url =
match->GetTemplateURL(template_url_service, false);
return url::GURLAndroid::FromNativeGURL(
env, GURL(template_url->url_ref().ReplaceSearchTerms(
action->search_terms_args,
template_url_service->search_terms_data())));
}
ScopedJavaLocalRef<jobject>
AutocompleteControllerAndroid::GetMatchingTabForSuggestion(
JNIEnv* env,
uintptr_t match_ptr) {
const auto& match = *reinterpret_cast<AutocompleteMatch*>(match_ptr);
return match.GetMatchingJavaTab().get(env);
}
void AutocompleteControllerAndroid::Shutdown() {
// Cancel all pending actions and clear any remaining matches.
autocomplete_controller_.reset();
Java_AutocompleteController_notifyNativeDestroyed(AttachCurrentThread(),
java_controller_);
}
// static
void AutocompleteControllerAndroid::EnsureFactoryBuilt() {
AutocompleteControllerAndroid::Factory::GetInstance();
}
void AutocompleteControllerAndroid::SetVoiceMatches(
JNIEnv* env,
const JavaParamRef<jobjectArray>& j_voice_matches,
const JavaParamRef<jfloatArray>& j_confidence_scores) {
auto* const voice_suggest_provider =
autocomplete_controller_->voice_suggest_provider();
DCHECK(voice_suggest_provider)
<< "Voice matches received with no registered VoiceSuggestProvider. "
<< "Either disable voice input, or provision VoiceSuggestProvider.";
std::vector<std::u16string> voice_matches;
std::vector<float> confidence_scores;
AppendJavaStringArrayToStringVector(env, j_voice_matches, &voice_matches);
JavaFloatArrayToFloatVector(env, j_confidence_scores, &confidence_scores);
DCHECK(voice_matches.size() == confidence_scores.size());
voice_suggest_provider->ClearCache();
for (size_t index = 0; index < voice_matches.size(); ++index) {
voice_suggest_provider->AddVoiceSuggestion(voice_matches[index],
confidence_scores[index]);
}
}
void AutocompleteControllerAndroid::OnSuggestionDropdownHeightChanged(
JNIEnv* env,
jint dropdown_height_with_keyboard_active_px,
jint suggestion_height_px) {
if (suggestion_height_px == 0) {
// Don't touch the group definitions.
return;
}
size_t num_visible_matches =
(size_t)(1.f * dropdown_height_with_keyboard_active_px /
suggestion_height_px +
0.5f);
if (num_visible_matches == 0) {
return;
}
AndroidNonZPSSection::set_num_visible_matches(num_visible_matches);
}
void AutocompleteControllerAndroid::CreateNavigationObserver(
JNIEnv* env,
uintptr_t navigation_handle_ptr,
uintptr_t match_ptr) {
if (!base::FeatureList::IsEnabled(omnibox::kOmniboxShortcutsAndroid)) {
return;
}
auto* navigation_handle =
reinterpret_cast<content::NavigationHandle*>(navigation_handle_ptr);
const auto& match = *reinterpret_cast<AutocompleteMatch*>(match_ptr);
ChromeOmniboxNavigationObserverAndroid::Create(navigation_handle, profile_,
input_.text(), match);
}
ScopedJavaLocalRef<jobject> AutocompleteControllerAndroid::GetJavaObject()
const {
return ScopedJavaLocalRef<jobject>(java_controller_);
}
AutocompleteControllerAndroid::~AutocompleteControllerAndroid() = default;
void AutocompleteControllerAndroid::OnResultChanged(
AutocompleteController* controller,
bool default_match_changed) {
if (!inside_synchronous_start_) {
NotifySuggestionsReceived(autocomplete_controller_->result());
}
}
void AutocompleteControllerAndroid::NotifySuggestionsReceived(
const AutocompleteResult& autocomplete_result) {
if (is_low_memory_device_ && !autocomplete_controller_->done() &&
base::FeatureList::IsEnabled(
omnibox::kSuppressIntermediateACUpdatesOnLowEndDevices)) {
return;
}
JNIEnv* env = AttachCurrentThread();
Java_AutocompleteController_onSuggestionsReceived(
env, java_controller_, autocomplete_result.GetOrCreateJavaObject(env),
autocomplete_controller_->done());
}
void AutocompleteControllerAndroid::WarmUpRenderProcess() const {
// It is ok for this to get called multiple times since all the requests
// will get de-duplicated to the first one.
content::SpareRenderProcessHostManager::Get().WarmupSpare(profile_);
}
// static
AutocompleteControllerAndroid*
AutocompleteControllerAndroid::Factory::GetForProfile(Profile* profile) {
return static_cast<AutocompleteControllerAndroid*>(
GetInstance()->GetServiceForBrowserContext(profile, true));
}
AutocompleteControllerAndroid::Factory*
AutocompleteControllerAndroid::Factory::GetInstance() {
return base::Singleton<AutocompleteControllerAndroid::Factory>::get();
}
AutocompleteControllerAndroid::Factory::Factory()
: ProfileKeyedServiceFactory(
"AutocompleteControllerAndroid",
ProfileSelections::BuildForRegularAndIncognito()) {
DependsOn(TemplateURLServiceFactory::GetInstance());
DependsOn(ShortcutsBackendFactory::GetInstance());
}
AutocompleteControllerAndroid::Factory::~Factory() = default;
std::unique_ptr<KeyedService>
AutocompleteControllerAndroid::Factory::BuildServiceInstanceForBrowserContext(
content::BrowserContext* context) const {
auto* profile = static_cast<Profile*>(context);
return std::make_unique<AutocompleteControllerAndroid>(
profile, std::make_unique<ChromeAutocompleteProviderClient>(profile),
false);
}
static ScopedJavaLocalRef<jobject> JNI_AutocompleteController_GetForProfile(
JNIEnv* env,
Profile* profile) {
AutocompleteControllerAndroid* native_bridge =
AutocompleteControllerAndroid::Factory::GetForProfile(profile);
if (!native_bridge) {
return {};
}
return native_bridge->GetJavaObject();
}