Commit 1aea053b authored by Renjie Liu's avatar Renjie Liu Committed by Commit Bot

Add GeoLanguageProvider interface.

Original author: amoylan@chromium.org
Original cl: https://chromium-review.googlesource.com/c/chromium/src/+/706439

This CL adds the GeoLanguageProvider browser singleton as described in
the design doc:
https://docs.google.com/document/d/18WqVHz5F9vaUiE32E8Ge6QHmku2QSJKvlqB9JjnIM-g

This interface provides a list of language codes corresponding to local
languages based on the device's approximate geolocation derived from
its public IP address, using the PublicIpAddressGeolocator service.
IP geolocation updates are requested no more often than every 24 hours.

This singleton is started up after Profile init in
PreMainMessageLoopRun. The startup runs on a background task runner,
connecting & subscribing to the IP geolocation service. Startup is gated
on the Feature setting "GeoLanguage" which is false for now.

Bug: 76915
Change-Id: I9af2b1375fabda3d029ca396043b9195acab7cf0
Reviewed-on: https://chromium-review.googlesource.com/807884
Commit-Queue: Renjie Liu <renjieliu@chromium.org>
Reviewed-by: default avatarMiguel Casas <mcasas@chromium.org>
Reviewed-by: default avatarZhongyi Shi <zhongyi@chromium.org>
Reviewed-by: default avatarColin Blundell <blundell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#523709}
parent e48a05cc
......@@ -1624,6 +1624,7 @@ split_static_library("browser") {
"//components/infobars/core",
"//components/invalidation/impl",
"//components/keyed_service/content",
"//components/language/content/browser",
"//components/language/core/browser",
"//components/metrics:call_stacks",
"//components/metrics:component_metrics",
......
......@@ -35,14 +35,34 @@ source_set("language_code_locator") {
]
}
static_library("browser") {
sources = [
"geo_language_provider.cc",
"geo_language_provider.h",
]
deps = [
":language_code_locator",
"//base",
"//net",
"//services/device/public/interfaces:interfaces",
"//services/service_manager/public/cpp",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"geo_language_provider_unittest.cc",
"language_code_locator_unittest.cc",
]
deps = [
":browser",
":language_code_locator",
"//base",
"//base/test:test_support",
"//services/device/public/interfaces:interfaces",
"//services/service_manager/public/cpp",
"//testing/gmock",
"//testing/gtest",
]
......
include_rules = [
"+content/public/browser",
"+device/geolocation/public/interfaces",
"+net",
"+services/device/public/interfaces",
"+services/service_manager/public",
]
// 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/memory/singleton.h"
#include "base/task_scheduler/post_task.h"
#include "base/time/time.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/device/public/interfaces/constants.mojom.h"
#include "services/device/public/interfaces/public_ip_address_geolocation_provider.mojom.h"
#include "services/service_manager/public/cpp/connector.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::TimeDelta::FromDays(1);
} // namespace
GeoLanguageProvider::GeoLanguageProvider()
: languages_(),
creation_task_runner_(base::SequencedTaskRunnerHandle::Get()),
background_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BACKGROUND,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {
// 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)
: languages_(),
creation_task_runner_(base::SequencedTaskRunnerHandle::Get()),
background_task_runner_(background_task_runner) {
// 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();
}
void GeoLanguageProvider::StartUp(
std::unique_ptr<service_manager::Connector> service_manager_connector) {
DCHECK_CALLED_ON_VALID_SEQUENCE(creation_sequence_checker_);
service_manager_connector_ = std::move(service_manager_connector);
// 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_;
}
void GeoLanguageProvider::BackgroundStartUp() {
// This binds background_sequence_checker_.
DCHECK_CALLED_ON_VALID_SEQUENCE(background_sequence_checker_);
// Initialize location->language lookup library.
language_code_locator_ = std::make_unique<language::LanguageCodeLocator>();
// Make initial query.
QueryNextPosition();
}
void GeoLanguageProvider::BindIpGeolocationService() {
DCHECK_CALLED_ON_VALID_SEQUENCE(background_sequence_checker_);
DCHECK(!geolocation_provider_.is_bound());
// Bind a PublicIpAddressGeolocationProvider.
device::mojom::PublicIpAddressGeolocationProviderPtr ip_geolocation_provider;
service_manager_connector_->BindInterface(
device::mojom::kServiceName, mojo::MakeRequest(&ip_geolocation_provider));
net::PartialNetworkTrafficAnnotationTag partial_traffic_annotation =
net::DefinePartialNetworkTrafficAnnotation("geo_language_provider",
"network_location_request",
R"(
semantics {
sender: "GeoLanguage Provider"
}
policy {
setting:
"Users can control this feature via the translation settings "
"'Languages', 'Language', 'Offer to translate'."
chrome_policy {
DefaultGeolocationSetting {
DefaultGeolocationSetting: 2
}
}
})");
// Use the PublicIpAddressGeolocationProvider to bind ip_geolocation_service_.
ip_geolocation_provider->CreateGeolocation(
static_cast<net::MutablePartialNetworkTrafficAnnotationTag>(
partial_traffic_annotation),
mojo::MakeRequest(&geolocation_provider_));
// 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_.encountered_error())
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_);
const std::vector<std::string> languages =
language_code_locator_->GetLanguageCode(geoposition->latitude,
geoposition->longitude);
// Update current languages on UI thread.
creation_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&GeoLanguageProvider::SetGeoLanguages,
base::Unretained(this), languages));
// 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::SetGeoLanguages(
const std::vector<std::string>& languages) {
DCHECK_CALLED_ON_VALID_SEQUENCE(creation_sequence_checker_);
languages_ = languages;
}
} // namespace language
// 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.
#ifndef COMPONENTS_LANGUAGE_CONTENT_BROWSER_GEO_LANGUAGE_PROVIDER_H_
#define COMPONENTS_LANGUAGE_CONTENT_BROWSER_GEO_LANGUAGE_PROVIDER_H_
#include <string>
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "components/language/content/browser/language_code_locator.h"
#include "device/geolocation/public/interfaces/geolocation.mojom.h"
namespace base {
template <typename T>
struct DefaultSingletonTraits;
}
namespace service_manager {
class Connector;
}
namespace language {
// GeoLanguageProvider is responsible for providing a "local" language derived
// from the approximate geolocation of the device based only on its public IP
// address.
// * Singleton class. Access through GetInstance().
// * Sequencing: Must be created and used on the same sequence.
class GeoLanguageProvider {
public:
static GeoLanguageProvider* GetInstance();
// Call this once near browser startup. Begins ongoing geo-language updates.
// * Initializes location->language mapping in a low-priority background task.
// * Until the first IP geolocation completes, CurrentGeoLanguages() will
// return an empty list.
// |service_manager_connector| should not yet be bound to a sequence, e.g., it
// should be the result of invoking ServiceManagerConnect::Clone() on another
// connector.
void StartUp(
std::unique_ptr<service_manager::Connector> service_manager_connector);
// Returns the inferred ranked list of local languages based on the most
// recently obtained approximate public-IP geolocation of the device.
// * Returns a list of BCP-47 language codes.
// * Returns an empty list in these cases:
// - StartUp() not yet called
// - Geolocation failed
// - Geolocation pending
// - Geolocation succeeded but no local language is mapped to that location
std::vector<std::string> CurrentGeoLanguages() const;
private:
friend class GeoLanguageProviderTest;
GeoLanguageProvider();
explicit GeoLanguageProvider(
scoped_refptr<base::SequencedTaskRunner> background_task_runner);
~GeoLanguageProvider();
friend struct base::DefaultSingletonTraits<GeoLanguageProvider>;
// Performs actual work described in StartUp() above.
void BackgroundStartUp();
// Binds |ip_geolocation_service_| using a service_manager::Connector.
void BindIpGeolocationService();
// Requests the next available IP-based approximate geolocation from
// |ip_geolocation_service_|, binding |ip_geolocation_service_| first if
// necessary.
void QueryNextPosition();
// Updates the list of BCP-47 language codes that will be returned by calls to
// CurrentGeoLanguages().
// Must be called on the UI thread.
void SetGeoLanguages(const std::vector<std::string>& languages);
// Callback for updates from |ip_geolocation_service_|.
void OnIpGeolocationResponse(device::mojom::GeopositionPtr geoposition);
// List of BCP-47 language code inferred from public-IP geolocation.
// May be empty. See comment on CurrentGeoLanguages() above.
std::vector<std::string> languages_;
// Service manager connector for use on background_task_runner_.
std::unique_ptr<service_manager::Connector> service_manager_connector_;
// Connection to the IP geolocation service.
device::mojom::GeolocationPtr geolocation_provider_;
// Location -> Language lookup library.
std::unique_ptr<language::LanguageCodeLocator> language_code_locator_;
// Runner for tasks that should run on the creation sequence.
scoped_refptr<base::SequencedTaskRunner> creation_task_runner_;
// Runner for low priority background tasks.
scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
// Sequence checker for methods that must run on the creation sequence.
SEQUENCE_CHECKER(creation_sequence_checker_);
// Sequence checker for background_task_runner_.
SEQUENCE_CHECKER(background_sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(GeoLanguageProvider);
};
} // namespace language
#endif // COMPONENTS_LANGUAGE_CONTENT_BROWSER_GEO_LANGUAGE_PROVIDER_H_
// 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 <memory>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/timer/timer.h"
#include "device/geolocation/public/interfaces/geolocation.mojom.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "services/device/public/interfaces/constants.mojom.h"
#include "services/device/public/interfaces/public_ip_address_geolocation_provider.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "services/service_manager/public/interfaces/connector.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
// Mock impl of mojom::Geolocation that allows tests to control the returned
// location.
class MockGeoLocation : public device::mojom::Geolocation {
public:
MockGeoLocation() : binding_(this) {}
// device::mojom::Geolocation implementation:
void SetHighAccuracy(bool high_accuracy) override {}
void QueryNextPosition(QueryNextPositionCallback callback) override {
++query_next_position_called_times_;
std::move(callback).Run(position_.Clone());
}
void BindGeoLocation(device::mojom::GeolocationRequest request) {
binding_.Bind(std::move(request));
}
void MoveToLocation(float latitude, float longitude) {
position_.latitude = latitude;
position_.longitude = longitude;
}
int query_next_position_called_times() const {
return query_next_position_called_times_;
}
private:
int query_next_position_called_times_ = 0;
device::mojom::Geoposition position_;
mojo::Binding<device::mojom::Geolocation> binding_;
};
// Mock impl of mojom::PublicIpAddressGeolocationProvider that binds Geolocation
// to testing impl.
class MockIpGeoLocationProvider
: public device::mojom::PublicIpAddressGeolocationProvider {
public:
explicit MockIpGeoLocationProvider(MockGeoLocation* mock_geo_location)
: mock_geo_location_(mock_geo_location), binding_(this) {}
void Bind(mojo::ScopedMessagePipeHandle handle) {
binding_.Bind(device::mojom::PublicIpAddressGeolocationProviderRequest(
std::move(handle)));
}
void CreateGeolocation(
const net::MutablePartialNetworkTrafficAnnotationTag& /* unused */,
device::mojom::GeolocationRequest request) override {
mock_geo_location_->BindGeoLocation(std::move(request));
}
private:
MockGeoLocation* mock_geo_location_;
mojo::Binding<device::mojom::PublicIpAddressGeolocationProvider> binding_;
};
} // namespace
namespace language {
class GeoLanguageProviderTest : public testing::Test {
public:
GeoLanguageProviderTest()
: task_runner_(base::MakeRefCounted<base::TestMockTimeTaskRunner>(
base::TestMockTimeTaskRunner::Type::kBoundToThread)),
scoped_context_(task_runner_.get()),
geo_language_provider_(task_runner_),
mock_ip_geo_location_provider_(&mock_geo_location_) {
service_manager::mojom::ConnectorRequest request;
connector_ = service_manager::Connector::Create(&request);
service_manager::Connector::TestApi test_api(connector_.get());
test_api.OverrideBinderForTesting(
device::mojom::kServiceName,
device::mojom::PublicIpAddressGeolocationProvider::Name_,
base::BindRepeating(&MockIpGeoLocationProvider::Bind,
base::Unretained(&mock_ip_geo_location_provider_)));
}
protected:
std::vector<std::string> GetCurrentGeoLanguages() {
return geo_language_provider_.CurrentGeoLanguages();
}
void StartGeoLanguageProvider() {
geo_language_provider_.StartUp(std::move(connector_));
}
void MoveToLocation(float latitude, float longitude) {
mock_geo_location_.MoveToLocation(latitude, longitude);
}
const scoped_refptr<base::TestMockTimeTaskRunner>& GetTaskRunner() {
return task_runner_;
}
int GetQueryNextPositionCalledTimes() {
return mock_geo_location_.query_next_position_called_times();
}
private:
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
const base::TestMockTimeTaskRunner::ScopedContext scoped_context_;
// Object under test.
GeoLanguageProvider geo_language_provider_;
MockGeoLocation mock_geo_location_;
MockIpGeoLocationProvider mock_ip_geo_location_provider_;
std::unique_ptr<service_manager::Connector> connector_;
};
TEST_F(GeoLanguageProviderTest, GetCurrentGeoLanguages) {
// Setup a random place in Madhya Pradesh, India.
MoveToLocation(23.0, 80.0);
StartGeoLanguageProvider();
const auto task_runner = GetTaskRunner();
task_runner->RunUntilIdle();
const std::vector<std::string>& result = GetCurrentGeoLanguages();
std::vector<std::string> expected_langs = {"hi", "mr", "ur"};
EXPECT_EQ(expected_langs, result);
EXPECT_EQ(1, GetQueryNextPositionCalledTimes());
}
TEST_F(GeoLanguageProviderTest, NoFrequentCalls) {
// Setup a random place in Madhya Pradesh, India.
MoveToLocation(23.0, 80.0);
StartGeoLanguageProvider();
const auto task_runner = GetTaskRunner();
task_runner->RunUntilIdle();
const std::vector<std::string>& result = GetCurrentGeoLanguages();
std::vector<std::string> expected_langs = {"hi", "mr", "ur"};
EXPECT_EQ(expected_langs, result);
task_runner->FastForwardBy(base::TimeDelta::FromHours(12));
EXPECT_EQ(1, GetQueryNextPositionCalledTimes());
}
TEST_F(GeoLanguageProviderTest, ButDoCallInTheNextDay) {
// Setup a random place in Madhya Pradesh, India.
MoveToLocation(23.0, 80.0);
StartGeoLanguageProvider();
const auto task_runner = GetTaskRunner();
task_runner->RunUntilIdle();
std::vector<std::string> result = GetCurrentGeoLanguages();
std::vector<std::string> expected_langs = {"hi", "mr", "ur"};
EXPECT_EQ(expected_langs, result);
// Move to another random place in Karnataka, India.
MoveToLocation(15.0, 75.0);
task_runner->FastForwardBy(base::TimeDelta::FromHours(25));
EXPECT_EQ(2, GetQueryNextPositionCalledTimes());
result = GetCurrentGeoLanguages();
std::vector<std::string> expected_langs_2 = {"kn", "ur", "te", "mr", "ta"};
EXPECT_EQ(expected_langs_2, result);
}
} // namespace language
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment