// Copyright (c) 2012 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 "device/geolocation/location_arbitrator.h" #include #include #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/memory/ptr_util.h" #include "build/build_config.h" #include "device/geolocation/network_location_provider.h" #include "device/geolocation/public/cpp/geoposition.h" #include "device/geolocation/wifi_polling_policy.h" namespace device { // To avoid oscillations, set this to twice the expected update interval of a // a GPS-type location provider (in case it misses a beat) plus a little. const base::TimeDelta LocationArbitrator::kFixStaleTimeoutTimeDelta = base::TimeDelta::FromSeconds(11); LocationArbitrator::LocationArbitrator( const CustomLocationProviderCallback& custom_location_provider_getter, GeolocationProvider::RequestContextProducer request_context_producer, const std::string& api_key) : custom_location_provider_getter_(custom_location_provider_getter), request_context_producer_(request_context_producer), api_key_(api_key), position_provider_(nullptr), is_permission_granted_(false), is_running_(false) {} LocationArbitrator::~LocationArbitrator() { // Release the global wifi polling policy state. WifiPollingPolicy::Shutdown(); } bool LocationArbitrator::HasPermissionBeenGrantedForTest() const { return is_permission_granted_; } void LocationArbitrator::OnPermissionGranted() { is_permission_granted_ = true; for (const auto& provider : providers_) provider->OnPermissionGranted(); } void LocationArbitrator::SetLastNetworkPosition( const mojom::Geoposition& position) { last_network_position_ = position; } const mojom::Geoposition& LocationArbitrator::GetLastNetworkPosition() { return last_network_position_; } void LocationArbitrator::StartProvider(bool enable_high_accuracy) { is_running_ = true; enable_high_accuracy_ = enable_high_accuracy; if (providers_.empty()) { RegisterSystemProvider(); // Request a URLRequestContextGetter to use for network geolocation. if (!request_context_producer_.is_null()) { // Note: .Reset() will cancel any previous callback. request_context_response_callback_.Reset( base::Bind(&LocationArbitrator::OnRequestContextResponse, base::Unretained(this))); // Invoke callback to obtain a URL request context. request_context_producer_.Run( request_context_response_callback_.callback()); return; } } DoStartProviders(); } void LocationArbitrator::DoStartProviders() { if (providers_.empty()) { // If no providers are available, we report an error to avoid // callers waiting indefinitely for a reply. mojom::Geoposition position; position.error_code = mojom::Geoposition::ErrorCode::POSITION_UNAVAILABLE; arbitrator_update_callback_.Run(this, position); return; } for (const auto& provider : providers_) { provider->StartProvider(enable_high_accuracy_); } } void LocationArbitrator::StopProvider() { // Reset the reference location state (provider+position) // so that future starts use fresh locations from // the newly constructed providers. position_provider_ = nullptr; position_ = mojom::Geoposition(); providers_.clear(); is_running_ = false; } void LocationArbitrator::OnRequestContextResponse( scoped_refptr context_getter) { if (context_getter != nullptr) { // Create a NetworkLocationProvider using the provided request context. RegisterProvider( NewNetworkLocationProvider(std::move(context_getter), api_key_)); } DoStartProviders(); } void LocationArbitrator::RegisterProvider( std::unique_ptr provider) { if (!provider) return; provider->SetUpdateCallback(base::Bind(&LocationArbitrator::OnLocationUpdate, base::Unretained(this))); if (is_permission_granted_) provider->OnPermissionGranted(); providers_.push_back(std::move(provider)); } void LocationArbitrator::RegisterSystemProvider() { std::unique_ptr provider; if (custom_location_provider_getter_) provider = custom_location_provider_getter_.Run(); // Use the default system provider if the custom provider is null. if (!provider) provider = NewSystemLocationProvider(); RegisterProvider(std::move(provider)); } void LocationArbitrator::OnLocationUpdate( const LocationProvider* provider, const mojom::Geoposition& new_position) { DCHECK(ValidateGeoposition(new_position) || new_position.error_code != mojom::Geoposition::ErrorCode::NONE); providers_polled_.insert(provider); if (IsNewPositionBetter(position_, new_position, provider == position_provider_)) { position_provider_ = provider; position_ = new_position; } // Don't fail until all providers had their say. if (ValidateGeoposition(position_) || (providers_polled_.size() == providers_.size())) arbitrator_update_callback_.Run(this, position_); } const mojom::Geoposition& LocationArbitrator::GetPosition() { return position_; } void LocationArbitrator::SetUpdateCallback( const LocationProviderUpdateCallback& callback) { DCHECK(!callback.is_null()); arbitrator_update_callback_ = callback; } std::unique_ptr LocationArbitrator::NewNetworkLocationProvider( scoped_refptr context, const std::string& api_key) { DCHECK(context != nullptr); #if defined(OS_ANDROID) // Android uses its own SystemLocationProvider. return nullptr; #else return std::make_unique(std::move(context), api_key, this); #endif } std::unique_ptr LocationArbitrator::NewSystemLocationProvider() { #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) || \ defined(OS_FUCHSIA) return nullptr; #else return device::NewSystemLocationProvider(); #endif } base::Time LocationArbitrator::GetTimeNow() const { return base::Time::Now(); } bool LocationArbitrator::IsNewPositionBetter( const mojom::Geoposition& old_position, const mojom::Geoposition& new_position, bool from_same_provider) const { // Updates location_info if it's better than what we currently have, // or if it's a newer update from the same provider. if (!ValidateGeoposition(old_position)) { // Older location wasn't locked. return true; } if (ValidateGeoposition(new_position)) { // New location is locked, let's check if it's any better. if (old_position.accuracy >= new_position.accuracy) { // Accuracy is better. return true; } else if (from_same_provider) { // Same provider, fresher location. return true; } else if (GetTimeNow() - old_position.timestamp > kFixStaleTimeoutTimeDelta) { // Existing fix is stale. return true; } } return false; } } // namespace device