blob: 87bd68f822c36729c21f89862147c2cd1cd33a09 [file] [log] [blame]
// Copyright (c) 2013 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 "chrome/browser/extensions/api/location/location_manager.h"
#include <math.h>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/macros.h"
#include "base/time/time.h"
#include "chrome/common/extensions/api/location.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/geolocation_provider.h"
#include "content/public/common/geoposition.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h"
#include "extensions/common/permissions/permission_set.h"
#include "extensions/common/permissions/permissions_data.h"
using content::BrowserThread;
// TODO(vadimt): Add tests.
namespace extensions {
namespace location = api::location;
namespace updatepolicy {
// Base class for all update policies for sending a location.
class UpdatePolicy : public base::RefCounted<UpdatePolicy> {
public:
UpdatePolicy() {}
// True if the caller should send an update based off of this policy.
virtual bool ShouldSendUpdate(const content::Geoposition&) const = 0;
// Updates any policy state on reporting a position.
virtual void OnPositionReported(const content::Geoposition&) = 0;
protected:
virtual ~UpdatePolicy() {}
private:
friend class base::RefCounted<UpdatePolicy>;
DISALLOW_COPY_AND_ASSIGN(UpdatePolicy);
};
// A policy that controls sending an update below a distance threshold.
class DistanceBasedUpdatePolicy : public UpdatePolicy {
public:
explicit DistanceBasedUpdatePolicy(double distance_update_threshold_meters) :
distance_update_threshold_meters_(distance_update_threshold_meters)
{}
// UpdatePolicy Implementation
bool ShouldSendUpdate(const content::Geoposition& position) const override {
return !last_updated_position_.Validate() ||
Distance(position.latitude,
position.longitude,
last_updated_position_.latitude,
last_updated_position_.longitude) >
distance_update_threshold_meters_;
}
void OnPositionReported(const content::Geoposition& position) override {
last_updated_position_ = position;
}
private:
~DistanceBasedUpdatePolicy() override {}
// Calculates the distance between two latitude and longitude points.
static double Distance(const double latitude1,
const double longitude1,
const double latitude2,
const double longitude2) {
// The earth has a radius of about 6371 km.
const double kRadius = 6371000;
const double kPi = 3.14159265358979323846;
const double kDegreesToRadians = kPi / 180.0;
// Conversions
const double latitude1Rad = latitude1 * kDegreesToRadians;
const double latitude2Rad = latitude2 * kDegreesToRadians;
const double latitudeDistRad = latitude2Rad - latitude1Rad;
const double longitudeDistRad = (longitude2 - longitude1) *
kDegreesToRadians;
// The Haversine Formula determines the great circle distance
// between two points on a sphere.
const double chordLengthSquared = pow(sin(latitudeDistRad / 2.0), 2) +
(pow(sin(longitudeDistRad / 2.0), 2) *
cos(latitude1Rad) *
cos(latitude2Rad));
const double angularDistance = 2.0 * atan2(sqrt(chordLengthSquared),
sqrt(1.0 - chordLengthSquared));
return kRadius * angularDistance;
}
const double distance_update_threshold_meters_;
content::Geoposition last_updated_position_;
DISALLOW_COPY_AND_ASSIGN(DistanceBasedUpdatePolicy);
};
// A policy that controls sending an update above a time threshold.
class TimeBasedUpdatePolicy : public UpdatePolicy {
public:
explicit TimeBasedUpdatePolicy(double time_between_updates_ms) :
time_between_updates_ms_(time_between_updates_ms)
{}
// UpdatePolicy Implementation
bool ShouldSendUpdate(const content::Geoposition&) const override {
return (base::Time::Now() - last_update_time_).InMilliseconds() >
time_between_updates_ms_;
}
void OnPositionReported(const content::Geoposition&) override {
last_update_time_ = base::Time::Now();
}
private:
~TimeBasedUpdatePolicy() override {}
base::Time last_update_time_;
const double time_between_updates_ms_;
DISALLOW_COPY_AND_ASSIGN(TimeBasedUpdatePolicy);
};
} // namespace updatepolicy
// Request created by chrome.location.watchLocation() call.
class LocationRequest : public base::RefCounted<LocationRequest> {
public:
LocationRequest(
LocationManager* location_manager,
const std::string& extension_id,
const std::string& request_name,
const double* distance_update_threshold_meters,
const double* time_between_updates_ms);
const std::string& request_name() const { return request_name_; }
private:
friend class base::RefCounted<LocationRequest>;
~LocationRequest();
void OnLocationUpdate(const content::Geoposition& position);
// Determines if all policies say to send a position update.
// If there are no policies, this always says yes.
bool ShouldSendUpdate(const content::Geoposition& position);
// Updates the policies on sending a position update.
void OnPositionReported(const content::Geoposition& position);
// Request name.
const std::string request_name_;
// Id of the owner extension.
const std::string extension_id_;
// Owning location manager.
LocationManager* location_manager_;
// Holds Update Policies.
typedef std::vector<scoped_refptr<updatepolicy::UpdatePolicy> >
UpdatePolicyVector;
UpdatePolicyVector update_policies_;
scoped_ptr<content::GeolocationProvider::Subscription>
geolocation_subscription_;
DISALLOW_COPY_AND_ASSIGN(LocationRequest);
};
LocationRequest::LocationRequest(
LocationManager* location_manager,
const std::string& extension_id,
const std::string& request_name,
const double* distance_update_threshold_meters,
const double* time_between_updates_ms)
: request_name_(request_name),
extension_id_(extension_id),
location_manager_(location_manager) {
// TODO(vadimt): use request_info.
if (time_between_updates_ms) {
update_policies_.push_back(
new updatepolicy::TimeBasedUpdatePolicy(
*time_between_updates_ms));
}
if (distance_update_threshold_meters) {
update_policies_.push_back(
new updatepolicy::DistanceBasedUpdatePolicy(
*distance_update_threshold_meters));
}
// TODO(vadimt): This can get a location cached by GeolocationProvider,
// contrary to the API definition which says that creating a location watch
// will get new location.
geolocation_subscription_ = content::GeolocationProvider::GetInstance()->
AddLocationUpdateCallback(
base::Bind(&LocationRequest::OnLocationUpdate,
base::Unretained(this)),
true);
}
LocationRequest::~LocationRequest() {
}
void LocationRequest::OnLocationUpdate(const content::Geoposition& position) {
if (ShouldSendUpdate(position)) {
OnPositionReported(position);
location_manager_->SendLocationUpdate(
extension_id_, request_name_, position);
}
}
bool LocationRequest::ShouldSendUpdate(const content::Geoposition& position) {
for (UpdatePolicyVector::iterator it = update_policies_.begin();
it != update_policies_.end();
++it) {
if (!((*it)->ShouldSendUpdate(position))) {
return false;
}
}
return true;
}
void LocationRequest::OnPositionReported(const content::Geoposition& position) {
for (UpdatePolicyVector::iterator it = update_policies_.begin();
it != update_policies_.end();
++it) {
(*it)->OnPositionReported(position);
}
}
LocationManager::LocationManager(content::BrowserContext* context)
: browser_context_(context), extension_registry_observer_(this) {
extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
}
void LocationManager::AddLocationRequest(
const std::string& extension_id,
const std::string& request_name,
const double* distance_update_threshold_meters,
const double* time_between_updates_ms) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// TODO(vadimt): Consider resuming requests after restarting the browser.
// Override any old request with the same name.
RemoveLocationRequest(extension_id, request_name);
LocationRequestPointer location_request =
new LocationRequest(this,
extension_id,
request_name,
distance_update_threshold_meters,
time_between_updates_ms);
location_requests_.insert(
LocationRequestMap::value_type(extension_id, location_request));
}
void LocationManager::RemoveLocationRequest(const std::string& extension_id,
const std::string& name) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::pair<LocationRequestMap::iterator, LocationRequestMap::iterator>
extension_range = location_requests_.equal_range(extension_id);
for (LocationRequestMap::iterator it = extension_range.first;
it != extension_range.second;
++it) {
if (it->second->request_name() == name) {
location_requests_.erase(it);
return;
}
}
}
LocationManager::~LocationManager() {
}
void LocationManager::GeopositionToApiCoordinates(
const content::Geoposition& position,
location::Coordinates* coordinates) {
coordinates->latitude = position.latitude;
coordinates->longitude = position.longitude;
if (position.altitude > -10000.)
coordinates->altitude.reset(new double(position.altitude));
coordinates->accuracy = position.accuracy;
if (position.altitude_accuracy >= 0.) {
coordinates->altitude_accuracy.reset(
new double(position.altitude_accuracy));
}
if (position.heading >= 0. && position.heading <= 360.)
coordinates->heading.reset(new double(position.heading));
if (position.speed >= 0.)
coordinates->speed.reset(new double(position.speed));
}
void LocationManager::SendLocationUpdate(
const std::string& extension_id,
const std::string& request_name,
const content::Geoposition& position) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
scoped_ptr<base::ListValue> args(new base::ListValue());
events::HistogramValue histogram_value = events::UNKNOWN;
std::string event_name;
if (position.Validate() &&
position.error_code == content::Geoposition::ERROR_CODE_NONE) {
// Set data for onLocationUpdate event.
location::Location location;
location.name = request_name;
GeopositionToApiCoordinates(position, &location.coords);
location.timestamp = position.timestamp.ToJsTime();
args->Append(location.ToValue().release());
histogram_value = events::LOCATION_ON_LOCATION_UPDATE;
event_name = location::OnLocationUpdate::kEventName;
} else {
// Set data for onLocationError event.
// TODO(vadimt): Set name.
args->AppendString(position.error_message);
histogram_value = events::LOCATION_ON_LOCATION_ERROR;
event_name = location::OnLocationError::kEventName;
}
scoped_ptr<Event> event(
new Event(histogram_value, event_name, std::move(args)));
EventRouter::Get(browser_context_)
->DispatchEventToExtension(extension_id, std::move(event));
}
void LocationManager::OnExtensionLoaded(
content::BrowserContext* browser_context,
const Extension* extension) {
// Grants permission to use geolocation once an extension with "location"
// permission is loaded.
if (extension->permissions_data()->HasAPIPermission(
APIPermission::kLocation)) {
content::GeolocationProvider::GetInstance()
->UserDidOptIntoLocationServices();
}
}
void LocationManager::OnExtensionUnloaded(
content::BrowserContext* browser_context,
const Extension* extension,
UnloadedExtensionInfo::Reason reason) {
// Delete all requests from the unloaded extension.
location_requests_.erase(extension->id());
}
static base::LazyInstance<BrowserContextKeyedAPIFactory<LocationManager> >
g_factory = LAZY_INSTANCE_INITIALIZER;
// static
BrowserContextKeyedAPIFactory<LocationManager>*
LocationManager::GetFactoryInstance() {
return g_factory.Pointer();
}
// static
LocationManager* LocationManager::Get(content::BrowserContext* context) {
return BrowserContextKeyedAPIFactory<LocationManager>::Get(context);
}
} // namespace extensions