root/chrome/browser/extensions/api/location/location_manager.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ShouldSendUpdate
  2. OnPositionReported
  3. Distance
  4. ShouldSendUpdate
  5. OnPositionReported
  6. request_name
  7. Initialize
  8. GrantPermission
  9. AddObserverOnIOThread
  10. OnLocationUpdate
  11. ShouldSendUpdate
  12. OnPositionReported
  13. AddLocationRequest
  14. RemoveLocationRequest
  15. GeopositionToApiCoordinates
  16. SendLocationUpdate
  17. Observe
  18. GetFactoryInstance
  19. Get

// 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 <vector>

#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/time/time.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/profiles/profile.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/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "content/public/common/geoposition.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h"
#include "extensions/common/permissions/permission_set.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:
  explicit 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
  virtual 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_;
  }

  virtual void OnPositionReported(const content::Geoposition& position)
      OVERRIDE {
    last_updated_position_ = position;
  }

 private:
  virtual ~DistanceBasedUpdatePolicy() {}

  // 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
  virtual bool ShouldSendUpdate(const content::Geoposition&) const OVERRIDE {
    return (base::Time::Now() - last_update_time_).InMilliseconds() >
        time_between_updates_ms_;
  }

  virtual void OnPositionReported(const content::Geoposition&) OVERRIDE {
    last_update_time_ = base::Time::Now();
  }

 private:
  virtual ~TimeBasedUpdatePolicy() {}

  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.
// Lives in the IO thread, except for the constructor.
class LocationRequest
    : public base::RefCountedThreadSafe<LocationRequest,
                                        BrowserThread::DeleteOnIOThread> {
 public:
  LocationRequest(
      const base::WeakPtr<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);

  // Finishes the necessary setup for this object.
  // Call this method immediately after taking a strong reference
  // to this object.
  //
  // Ideally, we would do this at construction time, but currently
  // our refcount starts at zero. BrowserThread::PostTask will take a ref
  // and potentially release it before we are done, destroying us in the
  // constructor.
  void Initialize();

  const std::string& request_name() const { return request_name_; }

  // Grants permission for using geolocation.
  static void GrantPermission();

 private:
  friend class base::DeleteHelper<LocationRequest>;
  friend struct BrowserThread::DeleteOnThread<BrowserThread::IO>;

  virtual ~LocationRequest();

  void AddObserverOnIOThread();

  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.
  const base::WeakPtr<LocationManager> location_manager_;

  // Holds Update Policies.
  typedef std::vector<scoped_refptr<updatepolicy::UpdatePolicy> >
      UpdatePolicyVector;
  UpdatePolicyVector update_policies_;

  content::GeolocationProvider::LocationUpdateCallback callback_;

  DISALLOW_COPY_AND_ASSIGN(LocationRequest);
};

LocationRequest::LocationRequest(
    const base::WeakPtr<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.
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  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));
  }
}

void LocationRequest::Initialize() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  callback_ = base::Bind(&LocationRequest::OnLocationUpdate,
                         base::Unretained(this));

  BrowserThread::PostTask(
      BrowserThread::IO,
      FROM_HERE,
      base::Bind(&LocationRequest::AddObserverOnIOThread,
                 this));
}

void LocationRequest::GrantPermission() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  content::GeolocationProvider::GetInstance()->UserDidOptIntoLocationServices();
}

LocationRequest::~LocationRequest() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  content::GeolocationProvider::GetInstance()->RemoveLocationUpdateCallback(
      callback_);
}

void LocationRequest::AddObserverOnIOThread() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  // 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.
  content::GeolocationProvider::GetInstance()->AddLocationUpdateCallback(
      callback_, true);
}

void LocationRequest::OnLocationUpdate(const content::Geoposition& position) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (ShouldSendUpdate(position)) {
    OnPositionReported(position);
    BrowserThread::PostTask(
        BrowserThread::UI,
        FROM_HERE,
        base::Bind(&LocationManager::SendLocationUpdate,
                   location_manager_,
                   extension_id_,
                   request_name_,
                   position));
  }
}

bool LocationRequest::ShouldSendUpdate(const content::Geoposition& position) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  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) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  for (UpdatePolicyVector::iterator it = update_policies_.begin();
       it != update_policies_.end();
       ++it) {
    (*it)->OnPositionReported(position);
  }
}

LocationManager::LocationManager(content::BrowserContext* context)
    : profile_(Profile::FromBrowserContext(context)) {
  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
                 content::Source<Profile>(profile_));
  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
                 content::Source<Profile>(profile_));
}

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(AsWeakPtr(),
                          extension_id,
                          request_name,
                          distance_update_threshold_meters,
                          time_between_updates_ms);
  location_request->Initialize();
  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());
  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());
    event_name = location::OnLocationUpdate::kEventName;
  } else {
    // Set data for onLocationError event.
    // TODO(vadimt): Set name.
    args->AppendString(position.error_message);
    event_name = location::OnLocationError::kEventName;
  }

  scoped_ptr<Event> event(new Event(event_name, args.Pass()));

  ExtensionSystem::Get(profile_)->event_router()->
      DispatchEventToExtension(extension_id, event.Pass());
}

void LocationManager::Observe(int type,
                              const content::NotificationSource& source,
                              const content::NotificationDetails& details) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  switch (type) {
    case chrome::NOTIFICATION_EXTENSION_LOADED: {
      // Grants permission to use geolocation once an extension with "location"
      // permission is loaded.
      const Extension* extension =
          content::Details<const Extension>(details).ptr();

      if (extension->HasAPIPermission(APIPermission::kLocation)) {
          BrowserThread::PostTask(
              BrowserThread::IO,
              FROM_HERE,
              base::Bind(&LocationRequest::GrantPermission));
      }
      break;
    }
    case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: {
      // Delete all requests from the unloaded extension.
      const Extension* extension =
          content::Details<const UnloadedExtensionInfo>(details)->extension;
      location_requests_.erase(extension->id());
      break;
    }
    default:
      NOTREACHED();
      break;
  }
}

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

/* [<][>][^][v][top][bottom][index][help] */