1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "content/browser/geolocation/gps_location_provider_linux.h" 6 7 #include <errno.h> 8 9 #include <algorithm> 10 #include <cmath> 11 12 #include "base/bind.h" 13 #include "base/compiler_specific.h" 14 #include "base/logging.h" 15 #include "base/memory/scoped_ptr.h" 16 #include "base/message_loop/message_loop.h" 17 #include "base/strings/stringprintf.h" 18 #include "content/public/common/geoposition.h" 19 20 namespace content { 21 namespace { 22 23 const int kGpsdReconnectRetryIntervalMillis = 10 * 1000; 24 25 // As per http://gpsd.berlios.de/performance.html#id374524, poll twice per sec. 26 const int kPollPeriodMovingMillis = 500; 27 28 // Poll less frequently whilst stationary. 29 const int kPollPeriodStationaryMillis = kPollPeriodMovingMillis * 3; 30 31 // GPS reading must differ by more than this amount to be considered movement. 32 const int kMovementThresholdMeters = 20; 33 34 // This algorithm is reused from the corresponding code in the Gears project. 35 // The arbitrary delta is decreased (Gears used 100 meters); if we need to 36 // decrease it any further we'll likely want to do some smarter filtering to 37 // remove GPS location jitter noise. 38 bool PositionsDifferSiginificantly(const Geoposition& position_1, 39 const Geoposition& position_2) { 40 const bool pos_1_valid = position_1.Validate(); 41 if (pos_1_valid != position_2.Validate()) 42 return true; 43 if (!pos_1_valid) { 44 DCHECK(!position_2.Validate()); 45 return false; 46 } 47 double delta = std::sqrt( 48 std::pow(std::fabs(position_1.latitude - position_2.latitude), 2) + 49 std::pow(std::fabs(position_1.longitude - position_2.longitude), 2)); 50 // Convert to meters. 1 minute of arc of latitude (or longitude at the 51 // equator) is 1 nautical mile or 1852m. 52 delta *= 60 * 1852; 53 return delta > kMovementThresholdMeters; 54 } 55 56 } // namespace 57 58 #if defined(USE_LIBGPS) 59 60 // See http://crbug.com/103751. 61 COMPILE_ASSERT(GPSD_API_MAJOR_VERSION == 5, GPSD_API_version_is_not_5); 62 63 namespace { 64 65 const char kLibGpsName[] = "libgps.so.20"; 66 67 } // namespace 68 69 LibGps::LibGps() 70 : gps_data_(new gps_data_t), 71 is_open_(false) { 72 } 73 74 LibGps::~LibGps() { 75 Stop(); 76 } 77 78 LibGps* LibGps::New() { 79 scoped_ptr<LibGps> libgps(new LibGps); 80 if (!libgps->libgps_loader_.Load(kLibGpsName)) 81 return NULL; 82 83 return libgps.release(); 84 } 85 86 bool LibGps::Start() { 87 if (is_open_) 88 return true; 89 90 errno = 0; 91 if (libgps_loader_.gps_open(GPSD_SHARED_MEMORY, 0, gps_data_.get()) != 0) { 92 // See gps.h NL_NOxxx for definition of gps_open() error numbers. 93 DLOG(WARNING) << "gps_open() failed " << errno; 94 return false; 95 } 96 97 is_open_ = true; 98 return true; 99 } 100 101 void LibGps::Stop() { 102 if (is_open_) 103 libgps_loader_.gps_close(gps_data_.get()); 104 is_open_ = false; 105 } 106 107 bool LibGps::Read(Geoposition* position) { 108 DCHECK(position); 109 position->error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE; 110 if (!is_open_) { 111 DLOG(WARNING) << "No gpsd connection"; 112 position->error_message = "No gpsd connection"; 113 return false; 114 } 115 116 if (libgps_loader_.gps_read(gps_data_.get()) < 0) { 117 DLOG(WARNING) << "gps_read() fails"; 118 position->error_message = "gps_read() fails"; 119 return false; 120 } 121 122 if (!GetPositionIfFixed(position)) { 123 DLOG(WARNING) << "No fixed position"; 124 position->error_message = "No fixed position"; 125 return false; 126 } 127 128 position->error_code = Geoposition::ERROR_CODE_NONE; 129 position->timestamp = base::Time::Now(); 130 if (!position->Validate()) { 131 // GetPositionIfFixed returned true, yet we've not got a valid fix. 132 // This shouldn't happen; something went wrong in the conversion. 133 NOTREACHED() << "Invalid position from GetPositionIfFixed: lat,long " 134 << position->latitude << "," << position->longitude 135 << " accuracy " << position->accuracy << " time " 136 << position->timestamp.ToDoubleT(); 137 position->error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE; 138 position->error_message = "Bad fix from gps"; 139 return false; 140 } 141 return true; 142 } 143 144 bool LibGps::GetPositionIfFixed(Geoposition* position) { 145 DCHECK(position); 146 if (gps_data_->status == STATUS_NO_FIX) { 147 DVLOG(2) << "Status_NO_FIX"; 148 return false; 149 } 150 151 if (isnan(gps_data_->fix.latitude) || isnan(gps_data_->fix.longitude)) { 152 DVLOG(2) << "No valid lat/lon value"; 153 return false; 154 } 155 156 position->latitude = gps_data_->fix.latitude; 157 position->longitude = gps_data_->fix.longitude; 158 159 if (!isnan(gps_data_->fix.epx) && !isnan(gps_data_->fix.epy)) { 160 position->accuracy = std::max(gps_data_->fix.epx, gps_data_->fix.epy); 161 } else if (isnan(gps_data_->fix.epx) && !isnan(gps_data_->fix.epy)) { 162 position->accuracy = gps_data_->fix.epy; 163 } else if (!isnan(gps_data_->fix.epx) && isnan(gps_data_->fix.epy)) { 164 position->accuracy = gps_data_->fix.epx; 165 } else { 166 // TODO(joth): Fixme. This is a workaround for http://crbug.com/99326 167 DVLOG(2) << "libgps reported accuracy NaN, forcing to zero"; 168 position->accuracy = 0; 169 } 170 171 if (gps_data_->fix.mode == MODE_3D && !isnan(gps_data_->fix.altitude)) { 172 position->altitude = gps_data_->fix.altitude; 173 if (!isnan(gps_data_->fix.epv)) 174 position->altitude_accuracy = gps_data_->fix.epv; 175 } 176 177 if (!isnan(gps_data_->fix.track)) 178 position->heading = gps_data_->fix.track; 179 if (!isnan(gps_data_->fix.speed)) 180 position->speed = gps_data_->fix.speed; 181 return true; 182 } 183 184 #else // !defined(USE_LIBGPS) 185 186 // Stub implementation of LibGps. 187 LibGps::LibGps() { 188 } 189 190 LibGps::~LibGps() { 191 } 192 193 LibGps* LibGps::New() { 194 return NULL; 195 } 196 197 bool LibGps::Start() { 198 return false; 199 } 200 201 void LibGps::Stop() { 202 } 203 204 bool LibGps::Read(Geoposition* position) { 205 return false; 206 } 207 208 bool LibGps::GetPositionIfFixed(Geoposition* position) { 209 return false; 210 } 211 212 #endif // !defined(USE_LIBGPS) 213 214 GpsLocationProviderLinux::GpsLocationProviderLinux(LibGpsFactory libgps_factory) 215 : gpsd_reconnect_interval_millis_(kGpsdReconnectRetryIntervalMillis), 216 poll_period_moving_millis_(kPollPeriodMovingMillis), 217 poll_period_stationary_millis_(kPollPeriodStationaryMillis), 218 libgps_factory_(libgps_factory), 219 weak_factory_(this) { 220 DCHECK(libgps_factory_); 221 } 222 223 GpsLocationProviderLinux::~GpsLocationProviderLinux() { 224 } 225 226 bool GpsLocationProviderLinux::StartProvider(bool high_accuracy) { 227 if (!high_accuracy) { 228 StopProvider(); 229 return true; // Not an error condition, so still return true. 230 } 231 if (gps_ != NULL) { 232 DCHECK(weak_factory_.HasWeakPtrs()); 233 return true; 234 } 235 position_.error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE; 236 gps_.reset(libgps_factory_()); 237 if (gps_ == NULL) { 238 DLOG(WARNING) << "libgps could not be loaded"; 239 return false; 240 } 241 ScheduleNextGpsPoll(0); 242 return true; 243 } 244 245 void GpsLocationProviderLinux::StopProvider() { 246 weak_factory_.InvalidateWeakPtrs(); 247 gps_.reset(); 248 } 249 250 void GpsLocationProviderLinux::GetPosition(Geoposition* position) { 251 DCHECK(position); 252 *position = position_; 253 DCHECK(position->Validate() || 254 position->error_code != Geoposition::ERROR_CODE_NONE); 255 } 256 257 void GpsLocationProviderLinux::RequestRefresh() { 258 ScheduleNextGpsPoll(0); 259 } 260 261 void GpsLocationProviderLinux::OnPermissionGranted() { 262 } 263 264 void GpsLocationProviderLinux::DoGpsPollTask() { 265 if (!gps_->Start()) { 266 DLOG(WARNING) << "Couldn't start GPS provider."; 267 ScheduleNextGpsPoll(gpsd_reconnect_interval_millis_); 268 return; 269 } 270 271 Geoposition new_position; 272 if (!gps_->Read(&new_position)) { 273 ScheduleNextGpsPoll(poll_period_stationary_millis_); 274 return; 275 } 276 277 DCHECK(new_position.Validate() || 278 new_position.error_code != Geoposition::ERROR_CODE_NONE); 279 const bool differ = PositionsDifferSiginificantly(position_, new_position); 280 ScheduleNextGpsPoll(differ ? poll_period_moving_millis_ : 281 poll_period_stationary_millis_); 282 if (differ || new_position.error_code != Geoposition::ERROR_CODE_NONE) { 283 // Update if the new location is interesting or we have an error to report. 284 position_ = new_position; 285 NotifyCallback(position_); 286 } 287 } 288 289 void GpsLocationProviderLinux::ScheduleNextGpsPoll(int interval) { 290 weak_factory_.InvalidateWeakPtrs(); 291 base::MessageLoop::current()->PostDelayedTask( 292 FROM_HERE, 293 base::Bind(&GpsLocationProviderLinux::DoGpsPollTask, 294 weak_factory_.GetWeakPtr()), 295 base::TimeDelta::FromMilliseconds(interval)); 296 } 297 298 LocationProvider* NewSystemLocationProvider() { 299 return new GpsLocationProviderLinux(LibGps::New); 300 } 301 302 } // namespace content 303