#include "services/device/geolocation/location_provider_manager.h"
#include <map>
#include <memory>
#include <utility>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "components/device_event_log/device_event_log.h"
#include "services/device/geolocation/network_location_provider.h"
#include "services/device/geolocation/wifi_polling_policy.h"
#include "services/device/public/cpp/device_features.h"
#include "services/device/public/cpp/geolocation/geoposition.h"
#include "services/device/public/mojom/geolocation_internals.mojom.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace device {
namespace {
std::string_view LocationProviderManagerModeAsString(
mojom::LocationProviderManagerMode mode) {
switch (mode) {
case mojom::LocationProviderManagerMode::kNetworkOnly:
return "kNetworkOnly";
case mojom::LocationProviderManagerMode::kPlatformOnly:
return "kPlatformOnly";
case mojom::LocationProviderManagerMode::kCustomOnly:
return "kCustomOnly";
case mojom::LocationProviderManagerMode::kHybridPlatform:
return "kHybridPlatform";
case mojom::LocationProviderManagerMode::kHybridPlatform2:
return "kHybridPlatform2";
case mojom::LocationProviderManagerMode::kHybridFallbackNetwork:
return "kHybridFallbackNetwork";
}
NOTREACHED() << "LocationProviderManagerModeAsString: Unexpected mode: ";
}
}
using ::device::mojom::LocationProviderManagerMode::kCustomOnly;
using ::device::mojom::LocationProviderManagerMode::kHybridFallbackNetwork;
using ::device::mojom::LocationProviderManagerMode::kHybridPlatform;
using ::device::mojom::LocationProviderManagerMode::kHybridPlatform2;
using ::device::mojom::LocationProviderManagerMode::kNetworkOnly;
using ::device::mojom::LocationProviderManagerMode::kPlatformOnly;
enum class LocationProviderManagerSource {
kNetworkProvider = 0,
kPlatformProvider = 1,
kCustomProvider = 2,
kMaxValue = kCustomProvider,
};
LocationProviderManager::LocationProviderManager(
CustomLocationProviderCallback custom_location_provider_getter,
GeolocationSystemPermissionManager* geolocation_system_permission_manager,
const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
const std::string& api_key,
std::unique_ptr<PositionCache> position_cache,
base::RepeatingClosure internals_updated_closure,
NetworkLocationProvider::NetworkRequestCallback network_request_callback,
NetworkLocationProvider::NetworkResponseCallback network_response_callback)
: custom_location_provider_getter_(
std::move(custom_location_provider_getter)),
geolocation_system_permission_manager_(
geolocation_system_permission_manager),
url_loader_factory_(url_loader_factory),
api_key_(api_key),
position_cache_(std::move(position_cache)),
internals_updated_closure_(std::move(internals_updated_closure)),
network_request_callback_(std::move(network_request_callback)),
network_response_callback_(std::move(network_response_callback)) {
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
provider_manager_mode_ = kPlatformOnly;
#elif BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
provider_manager_mode_ = kNetworkOnly;
#elif BUILDFLAG(IS_ARKWEB)
provider_manager_mode_ = kPlatformOnly;
#else
provider_manager_mode_ = features::kLocationProviderManagerParam.Get();
#endif
GEOLOCATION_LOG(DEBUG) << "LocationProviderManager::LocationProviderManager: "
"provider_manager_mode_ is initialized to "
<< LocationProviderManagerModeAsString(
provider_manager_mode_);
}
LocationProviderManager::~LocationProviderManager() {
WifiPollingPolicy::Shutdown();
}
bool LocationProviderManager::HasPermissionBeenGrantedForTest() const {
return is_permission_granted_;
}
void LocationProviderManager::OnPermissionGranted() {
is_permission_granted_ = true;
if (network_location_provider_) {
network_location_provider_->OnPermissionGranted();
}
if (platform_location_provider_) {
platform_location_provider_->OnPermissionGranted();
}
if (custom_location_provider_) {
custom_location_provider_->OnPermissionGranted();
}
}
void LocationProviderManager::StartProvider(bool enable_high_accuracy) {
if (!InitializeProvider()) {
location_update_callback_.Run(
this, mojom::GeopositionResult::NewError(mojom::GeopositionError::New(
mojom::GeopositionErrorCode::kPositionUnavailable, "", "")));
} else {
enable_high_accuracy_ = enable_high_accuracy;
is_running_ = true;
switch (provider_manager_mode_) {
case kCustomOnly:
custom_location_provider_->StartProvider(enable_high_accuracy_);
break;
case kNetworkOnly:
case kHybridFallbackNetwork:
network_location_provider_->StartProvider(enable_high_accuracy_);
break;
case kPlatformOnly:
case kHybridPlatform:
case kHybridPlatform2:
platform_location_provider_->StartProvider(enable_high_accuracy_);
}
}
}
void LocationProviderManager::StopProvider() {
network_location_provider_result_.reset();
platform_location_provider_result_.reset();
custom_location_provider_result_.reset();
network_location_provider_.reset();
platform_location_provider_.reset();
custom_location_provider_.reset();
is_running_ = false;
#if BUILDFLAG(IS_MAC)
provider_manager_mode_ = features::kLocationProviderManagerParam.Get();
GEOLOCATION_LOG(DEBUG) << "LocationProviderManager::StopProvider: Resetting "
"provider_manager_mode_ to "
<< LocationProviderManagerModeAsString(
provider_manager_mode_);
#endif
}
void LocationProviderManager::RegisterProvider(LocationProvider& provider) {
provider.SetUpdateCallback(base::BindRepeating(
&LocationProviderManager::OnLocationUpdate, base::Unretained(this)));
if (is_permission_granted_) {
provider.OnPermissionGranted();
}
}
bool LocationProviderManager::InitializeProvider() {
if (network_location_provider_ || platform_location_provider_ ||
custom_location_provider_) {
return true;
}
if (custom_location_provider_getter_) {
custom_location_provider_ = custom_location_provider_getter_.Run();
if (custom_location_provider_) {
provider_manager_mode_ = kCustomOnly;
}
}
switch (provider_manager_mode_) {
case kCustomOnly:
RegisterProvider(*custom_location_provider_.get());
break;
case kNetworkOnly:
case kHybridFallbackNetwork:
if (!url_loader_factory_) {
return false;
}
network_location_provider_ =
NewNetworkLocationProvider(url_loader_factory_, api_key_);
RegisterProvider(*network_location_provider_.get());
break;
case kPlatformOnly:
case kHybridPlatform:
case kHybridPlatform2:
platform_location_provider_ = NewSystemLocationProvider();
if (!platform_location_provider_) {
return false;
}
RegisterProvider(*platform_location_provider_.get());
}
return true;
}
void LocationProviderManager::OnLocationUpdate(
const LocationProvider* provider,
mojom::GeopositionResultPtr new_result) {
DCHECK(new_result);
DCHECK(new_result->is_error() ||
new_result->is_position() &&
ValidateGeoposition(*new_result->get_position()));
switch (provider_manager_mode_) {
case kHybridPlatform:
case kHybridPlatform2:
platform_location_provider_result_ = new_result.Clone();
if (new_result->is_error()) {
GEOLOCATION_LOG(DEBUG) << base::StringPrintf(
"LocationProviderManager::OnLocationUpdate: Error code %d is "
"received when in %s mode.",
(int)new_result->get_error()->error_code,
LocationProviderManagerModeAsString(provider_manager_mode_));
if (provider_manager_mode_ == kHybridPlatform2 ||
(provider_manager_mode_ == kHybridPlatform &&
new_result->get_error()->error_code ==
device::mojom::GeopositionErrorCode::kWifiDisabled)) {
provider_manager_mode_ = kHybridFallbackNetwork;
platform_location_provider_->StopProvider();
platform_location_provider_.reset();
network_location_provider_ =
NewNetworkLocationProvider(url_loader_factory_, api_key_);
RegisterProvider(*network_location_provider_.get());
network_location_provider_->StartProvider(enable_high_accuracy_);
internals_updated_closure_.Run();
return;
}
}
break;
case kPlatformOnly:
platform_location_provider_result_ = new_result.Clone();
break;
case kNetworkOnly:
case kHybridFallbackNetwork:
network_location_provider_result_ = new_result.Clone();
break;
case kCustomOnly:
custom_location_provider_result_ = new_result.Clone();
break;
}
if (new_result->is_position()) {
base::UmaHistogramEnumeration("Geolocation.LocationProviderManager.Mode",
provider_manager_mode_);
LocationProviderManagerSource source;
if (provider == network_location_provider_.get()) {
source = LocationProviderManagerSource::kNetworkProvider;
} else if (provider == platform_location_provider_.get()) {
source = LocationProviderManagerSource::kPlatformProvider;
} else if (provider == custom_location_provider_.get()) {
source = LocationProviderManagerSource::kCustomProvider;
} else {
#if BUILDFLAG(IS_ARKWEB)
GEOLOCATION_LOG(ERROR) << "OnLocationUpdate provider match error: "
"provider_manager_mode_="
<< LocationProviderManagerModeAsString(
provider_manager_mode_)
<< " set source to kPlatformProvider";
source = LocationProviderManagerSource::kPlatformProvider;
#else
NOTREACHED();
#endif
}
base::UmaHistogramEnumeration("Geolocation.LocationProviderManager.Source",
source);
}
location_update_callback_.Run(this, std::move(new_result));
}
const mojom::GeopositionResult* LocationProviderManager::GetPosition() {
switch (provider_manager_mode_) {
case kHybridPlatform:
case kHybridPlatform2:
case kPlatformOnly:
return platform_location_provider_result_.get();
case kNetworkOnly:
case kHybridFallbackNetwork:
return network_location_provider_result_.get();
case kCustomOnly:
return custom_location_provider_result_.get();
}
}
void LocationProviderManager::FillDiagnostics(
mojom::GeolocationDiagnostics& diagnostics) {
if (!is_running_) {
diagnostics.provider_state =
mojom::GeolocationDiagnostics::ProviderState::kStopped;
return;
}
diagnostics.location_provider_manager_mode = provider_manager_mode_;
switch (provider_manager_mode_) {
case kHybridPlatform:
case kHybridPlatform2:
case kPlatformOnly:
return platform_location_provider_->FillDiagnostics(diagnostics);
case kNetworkOnly:
case kHybridFallbackNetwork:
return network_location_provider_->FillDiagnostics(diagnostics);
case kCustomOnly:
return custom_location_provider_->FillDiagnostics(diagnostics);
}
if (position_cache_) {
diagnostics.position_cache_diagnostics =
mojom::PositionCacheDiagnostics::New();
position_cache_->FillDiagnostics(*diagnostics.position_cache_diagnostics);
}
if (WifiPollingPolicy::IsInitialized()) {
diagnostics.wifi_polling_policy_diagnostics =
mojom::WifiPollingPolicyDiagnostics::New();
WifiPollingPolicy::Get()->FillDiagnostics(
*diagnostics.wifi_polling_policy_diagnostics);
}
}
void LocationProviderManager::SetUpdateCallback(
const LocationProviderUpdateCallback& callback) {
DCHECK(!callback.is_null());
location_update_callback_ = callback;
}
std::unique_ptr<LocationProvider>
LocationProviderManager::NewNetworkLocationProvider(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const std::string& api_key) {
DCHECK(url_loader_factory);
#if BUILDFLAG(IS_ANDROID)
return nullptr;
#else
return std::make_unique<NetworkLocationProvider>(
std::move(url_loader_factory), api_key, position_cache_.get(),
internals_updated_closure_, network_request_callback_,
network_response_callback_);
#endif
}
std::unique_ptr<LocationProvider>
LocationProviderManager::NewSystemLocationProvider() {
#if BUILDFLAG(IS_APPLE)
CHECK(geolocation_system_permission_manager_);
return device::NewSystemLocationProvider(
geolocation_system_permission_manager_->GetSystemGeolocationSource());
#elif BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_ARKWEB)
return device::NewSystemLocationProvider();
#else
return nullptr;
#endif
}
}