| // Copyright 2017 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 "components/language/content/browser/geo_language_provider.h" |
| |
| #include "base/bind.h" |
| #include "base/memory/singleton.h" |
| #include "base/no_destructor.h" |
| #include "base/task/post_task.h" |
| #include "base/task/thread_pool.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "components/language/content/browser/language_code_locator_provider.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/device_service.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| |
| namespace language { |
| namespace { |
| |
| // Don't start requesting updates to IP-based approximation geolocation until |
| // this long after receiving the last one. |
| constexpr base::TimeDelta kMinUpdatePeriod = base::Days(1); |
| |
| GeoLanguageProvider::Binder& GetBinderOverride() { |
| static base::NoDestructor<GeoLanguageProvider::Binder> binder; |
| return *binder; |
| } |
| |
| } // namespace |
| |
| const char GeoLanguageProvider::kCachedGeoLanguagesPref[] = |
| "language.geo_language_provider.cached_geo_languages"; |
| |
| GeoLanguageProvider::GeoLanguageProvider() |
| : creation_task_runner_(base::SequencedTaskRunnerHandle::Get()), |
| background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::BEST_EFFORT, |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})), |
| prefs_(nullptr) { |
| // Constructor is not required to run on |background_task_runner_|: |
| DETACH_FROM_SEQUENCE(background_sequence_checker_); |
| } |
| |
| GeoLanguageProvider::GeoLanguageProvider( |
| scoped_refptr<base::SequencedTaskRunner> background_task_runner) |
| : creation_task_runner_(base::SequencedTaskRunnerHandle::Get()), |
| background_task_runner_(background_task_runner), |
| prefs_(nullptr) { |
| // Constructor is not required to run on |background_task_runner_|: |
| DETACH_FROM_SEQUENCE(background_sequence_checker_); |
| } |
| |
| GeoLanguageProvider::~GeoLanguageProvider() = default; |
| |
| // static |
| GeoLanguageProvider* GeoLanguageProvider::GetInstance() { |
| return base::Singleton<GeoLanguageProvider, base::LeakySingletonTraits< |
| GeoLanguageProvider>>::get(); |
| } |
| |
| // static |
| void GeoLanguageProvider::RegisterLocalStatePrefs( |
| PrefRegistrySimple* const registry) { |
| registry->RegisterListPref(kCachedGeoLanguagesPref); |
| } |
| |
| void GeoLanguageProvider::StartUp(PrefService* const prefs) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(creation_sequence_checker_); |
| |
| prefs_ = prefs; |
| |
| const base::ListValue* const cached_languages_list = |
| prefs_->GetList(kCachedGeoLanguagesPref); |
| for (const auto& language_value : cached_languages_list->GetList()) { |
| languages_.push_back(language_value.GetString()); |
| } |
| |
| // Continue startup in the background. |
| background_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&GeoLanguageProvider::BackgroundStartUp, |
| base::Unretained(this))); |
| } |
| |
| std::vector<std::string> GeoLanguageProvider::CurrentGeoLanguages() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(creation_sequence_checker_); |
| return languages_; |
| } |
| |
| // static |
| void GeoLanguageProvider::OverrideBinderForTesting(Binder binder) { |
| GetBinderOverride() = std::move(binder); |
| } |
| |
| void GeoLanguageProvider::BackgroundStartUp() { |
| // This binds background_sequence_checker_. |
| DCHECK_CALLED_ON_VALID_SEQUENCE(background_sequence_checker_); |
| |
| // Initialize location->language lookup library. |
| language_code_locator_ = GetLanguageCodeLocator(prefs_); |
| |
| // Make initial query. |
| QueryNextPosition(); |
| } |
| |
| void GeoLanguageProvider::BindIpGeolocationService() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(background_sequence_checker_); |
| DCHECK(!geolocation_provider_.is_bound()); |
| |
| mojo::Remote<device::mojom::PublicIpAddressGeolocationProvider> |
| ip_geolocation_provider; |
| auto receiver = ip_geolocation_provider.BindNewPipeAndPassReceiver(); |
| const auto& binder = GetBinderOverride(); |
| if (binder) { |
| binder.Run(std::move(receiver)); |
| } else { |
| content::GetDeviceService().BindPublicIpAddressGeolocationProvider( |
| std::move(receiver)); |
| } |
| |
| net::PartialNetworkTrafficAnnotationTag partial_traffic_annotation = |
| net::DefinePartialNetworkTrafficAnnotation("geo_language_provider", |
| "network_location_request", |
| R"( |
| semantics { |
| sender: "GeoLanguage Provider" |
| } |
| policy { |
| setting: |
| "Users can disable this feature for translation requests in " |
| "settings 'Languages', 'Language', 'Offer to translate'. Note " |
| "that users can still manually trigger this feature via the " |
| "right-click menu." |
| chrome_policy { |
| DefaultGeolocationSetting { |
| DefaultGeolocationSetting: 2 |
| } |
| } |
| })"); |
| |
| // Use the PublicIpAddressGeolocationProvider to bind ip_geolocation_service_. |
| ip_geolocation_provider->CreateGeolocation( |
| static_cast<net::MutablePartialNetworkTrafficAnnotationTag>( |
| partial_traffic_annotation), |
| geolocation_provider_.BindNewPipeAndPassReceiver()); |
| // No error handler required: If the connection is broken, QueryNextPosition |
| // will bind it again. |
| } |
| |
| void GeoLanguageProvider::QueryNextPosition() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(background_sequence_checker_); |
| |
| if (geolocation_provider_.is_bound() && !geolocation_provider_.is_connected()) |
| geolocation_provider_.reset(); |
| if (!geolocation_provider_.is_bound()) |
| BindIpGeolocationService(); |
| |
| geolocation_provider_->QueryNextPosition(base::BindOnce( |
| &GeoLanguageProvider::OnIpGeolocationResponse, base::Unretained(this))); |
| } |
| |
| void GeoLanguageProvider::OnIpGeolocationResponse( |
| device::mojom::GeopositionPtr geoposition) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(background_sequence_checker_); |
| |
| // Update current languages on UI thread. We pass the lat/long pair so that |
| // SetGeoLanguages can do the lookup on the UI thread. This is because the |
| // language provider could decide to cache the values, requiring interaction |
| // with the pref service. |
| creation_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&GeoLanguageProvider::LookupAndSetLanguages, |
| base::Unretained(this), geoposition->latitude, |
| geoposition->longitude)); |
| |
| // Post a task to request a fresh lookup after |kMinUpdatePeriod|. |
| background_task_runner_->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&GeoLanguageProvider::QueryNextPosition, |
| base::Unretained(this)), |
| kMinUpdatePeriod); |
| } |
| |
| void GeoLanguageProvider::LookupAndSetLanguages(double lat, double lon) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(creation_sequence_checker_); |
| // Perform the lookup here (as opposed to the geolocation callback), as the |
| // locator could cache the value in a pref, which must happen on the UI thread |
| // This behavior is factored out in this function in order for tests to be |
| // able to call SetGeoLanguages directly. |
| SetGeoLanguages(language_code_locator_->GetLanguageCodes(lat, lon)); |
| } |
| |
| void GeoLanguageProvider::SetGeoLanguages( |
| const std::vector<std::string>& languages) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(creation_sequence_checker_); |
| languages_ = languages; |
| |
| base::ListValue cache_list; |
| for (size_t i = 0; i < languages_.size(); ++i) { |
| cache_list.Set(i, std::make_unique<base::Value>(languages_[i])); |
| } |
| prefs_->Set(kCachedGeoLanguagesPref, cache_list); |
| } |
| |
| } // namespace language |