blob: ee9ae3a2f366a553eb84754d48e4e01e2da7d68c [file] [log] [blame]
// Copyright 2014 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/geolocation_service_impl.h"
#include <utility>
#include "base/bind.h"
#include "base/metrics/histogram.h"
#include "device/geolocation/geolocation_service_context.h"
namespace device {
namespace {
// Geoposition error codes for reporting in UMA.
enum GeopositionErrorCode {
// NOTE: Do not renumber these as that would confuse interpretation of
// previously logged data. When making changes, also update the enum list
// in tools/metrics/histograms/histograms.xml to keep it in sync.
// There was no error.
GEOPOSITION_ERROR_CODE_NONE = 0,
// User denied use of geolocation.
GEOPOSITION_ERROR_CODE_PERMISSION_DENIED = 1,
// Geoposition could not be determined.
GEOPOSITION_ERROR_CODE_POSITION_UNAVAILABLE = 2,
// Timeout.
GEOPOSITION_ERROR_CODE_TIMEOUT = 3,
// NOTE: Add entries only immediately above this line.
GEOPOSITION_ERROR_CODE_COUNT = 4
};
void RecordGeopositionErrorCode(Geoposition::ErrorCode error_code) {
GeopositionErrorCode code = GEOPOSITION_ERROR_CODE_NONE;
switch (error_code) {
case Geoposition::ERROR_CODE_NONE:
code = GEOPOSITION_ERROR_CODE_NONE;
break;
case Geoposition::ERROR_CODE_PERMISSION_DENIED:
code = GEOPOSITION_ERROR_CODE_PERMISSION_DENIED;
break;
case Geoposition::ERROR_CODE_POSITION_UNAVAILABLE:
code = GEOPOSITION_ERROR_CODE_POSITION_UNAVAILABLE;
break;
case Geoposition::ERROR_CODE_TIMEOUT:
code = GEOPOSITION_ERROR_CODE_TIMEOUT;
break;
}
UMA_HISTOGRAM_ENUMERATION("Geolocation.LocationUpdate.ErrorCode", code,
GEOPOSITION_ERROR_CODE_COUNT);
}
} // namespace
GeolocationServiceImpl::GeolocationServiceImpl(
mojo::InterfaceRequest<GeolocationService> request,
GeolocationServiceContext* context,
const base::Closure& update_callback)
: binding_(this, std::move(request)),
context_(context),
update_callback_(update_callback),
high_accuracy_(false),
has_position_to_report_(false) {
DCHECK(context_);
binding_.set_connection_error_handler(base::Bind(
&GeolocationServiceImpl::OnConnectionError, base::Unretained(this)));
}
GeolocationServiceImpl::~GeolocationServiceImpl() {
// Make sure to respond to any pending callback even without a valid position.
if (!position_callback_.is_null()) {
if (!current_position_.valid) {
current_position_.error_code = mojom::Geoposition::ErrorCode(
GEOPOSITION_ERROR_CODE_POSITION_UNAVAILABLE);
current_position_.error_message = mojo::String("");
}
ReportCurrentPosition();
}
}
void GeolocationServiceImpl::PauseUpdates() {
geolocation_subscription_.reset();
}
void GeolocationServiceImpl::ResumeUpdates() {
if (position_override_.Validate()) {
OnLocationUpdate(position_override_);
return;
}
StartListeningForUpdates();
}
void GeolocationServiceImpl::StartListeningForUpdates() {
geolocation_subscription_ =
GeolocationProvider::GetInstance()->AddLocationUpdateCallback(
base::Bind(&GeolocationServiceImpl::OnLocationUpdate,
base::Unretained(this)),
high_accuracy_);
}
void GeolocationServiceImpl::SetHighAccuracy(bool high_accuracy) {
UMA_HISTOGRAM_BOOLEAN(
"Geolocation.GeolocationDispatcherHostImpl.EnableHighAccuracy",
high_accuracy);
high_accuracy_ = high_accuracy;
if (position_override_.Validate()) {
OnLocationUpdate(position_override_);
return;
}
StartListeningForUpdates();
}
void GeolocationServiceImpl::QueryNextPosition(
const QueryNextPositionCallback& callback) {
if (!position_callback_.is_null()) {
DVLOG(1) << "Overlapped call to QueryNextPosition!";
OnConnectionError(); // Simulate a connection error.
return;
}
position_callback_ = callback;
if (has_position_to_report_)
ReportCurrentPosition();
}
void GeolocationServiceImpl::SetOverride(const Geoposition& position) {
position_override_ = position;
if (!position_override_.Validate()) {
ResumeUpdates();
}
geolocation_subscription_.reset();
OnLocationUpdate(position_override_);
}
void GeolocationServiceImpl::ClearOverride() {
position_override_ = Geoposition();
StartListeningForUpdates();
}
void GeolocationServiceImpl::OnConnectionError() {
context_->ServiceHadConnectionError(this);
// The above call deleted this instance, so the only safe thing to do is
// return.
}
void GeolocationServiceImpl::OnLocationUpdate(const Geoposition& position) {
RecordGeopositionErrorCode(position.error_code);
DCHECK(context_);
update_callback_.Run();
current_position_.valid = position.Validate();
current_position_.latitude = position.latitude;
current_position_.longitude = position.longitude;
current_position_.altitude = position.altitude;
current_position_.accuracy = position.accuracy;
current_position_.altitude_accuracy = position.altitude_accuracy;
current_position_.heading = position.heading;
current_position_.speed = position.speed;
current_position_.timestamp = position.timestamp.ToDoubleT();
current_position_.error_code =
mojom::Geoposition::ErrorCode(position.error_code);
current_position_.error_message = position.error_message;
has_position_to_report_ = true;
if (!position_callback_.is_null())
ReportCurrentPosition();
}
void GeolocationServiceImpl::ReportCurrentPosition() {
position_callback_.Run(current_position_.Clone());
position_callback_.Reset();
has_position_to_report_ = false;
}
} // namespace device