blob: 90773bf13281990709437581455ea6321a78eafd [file] [log] [blame]
// 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