Geolocation: log the number of wifi access points used to determine location.
[chromium-blink-merge.git] / content / browser / geolocation / network_location_request.cc
blob41f3cea1fbf030db36612b352b5b379a2ec21962
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.
5 #include "content/browser/geolocation/network_location_request.h"
7 #include <set>
8 #include <string>
10 #include "base/json/json_reader.h"
11 #include "base/json/json_writer.h"
12 #include "base/metrics/histogram.h"
13 #include "base/metrics/sparse_histogram.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/values.h"
17 #include "content/browser/geolocation/location_arbitrator_impl.h"
18 #include "content/public/common/geoposition.h"
19 #include "google_apis/google_api_keys.h"
20 #include "net/base/escape.h"
21 #include "net/base/load_flags.h"
22 #include "net/url_request/url_fetcher.h"
23 #include "net/url_request/url_request_context_getter.h"
24 #include "net/url_request/url_request_status.h"
26 namespace content {
27 namespace {
29 const char kAccessTokenString[] = "accessToken";
30 const char kLocationString[] = "location";
31 const char kLatitudeString[] = "lat";
32 const char kLongitudeString[] = "lng";
33 const char kAccuracyString[] = "accuracy";
35 enum NetworkLocationRequestEvent {
36 // NOTE: Do not renumber these as that would confuse interpretation of
37 // previously logged data. When making changes, also update the enum list
38 // in tools/metrics/histograms/histograms.xml to keep it in sync.
39 NETWORK_LOCATION_REQUEST_EVENT_REQUEST_START = 0,
40 NETWORK_LOCATION_REQUEST_EVENT_REQUEST_CANCEL = 1,
41 NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_SUCCESS = 2,
42 NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_NOT_OK = 3,
43 NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_EMPTY = 4,
44 NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_MALFORMED = 5,
45 NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_INVALID_FIX = 6,
47 // NOTE: Add entries only immediately above this line.
48 NETWORK_LOCATION_REQUEST_EVENT_COUNT = 7
51 void RecordUmaEvent(NetworkLocationRequestEvent event) {
52 UMA_HISTOGRAM_ENUMERATION("Geolocation.NetworkLocationRequest.Event",
53 event, NETWORK_LOCATION_REQUEST_EVENT_COUNT);
56 void RecordUmaResponseCode(int code) {
57 UMA_HISTOGRAM_SPARSE_SLOWLY("Geolocation.NetworkLocationRequest.ResponseCode",
58 code);
61 void RecordUmaAccessPoints(int count) {
62 const int min = 0;
63 const int max = 10;
64 const int buckets = 11;
65 UMA_HISTOGRAM_CUSTOM_COUNTS("Geolocation.NetworkLocationRequest.AccessPoints",
66 count, min, max, buckets);
69 // Local functions
70 // Creates the request url to send to the server.
71 GURL FormRequestURL(const GURL& url);
73 void FormUploadData(const WifiData& wifi_data,
74 const base::Time& timestamp,
75 const string16& access_token,
76 std::string* upload_data);
78 // Attempts to extract a position from the response. Detects and indicates
79 // various failure cases.
80 void GetLocationFromResponse(bool http_post_result,
81 int status_code,
82 const std::string& response_body,
83 const base::Time& timestamp,
84 const GURL& server_url,
85 Geoposition* position,
86 string16* access_token);
88 // Parses the server response body. Returns true if parsing was successful.
89 // Sets |*position| to the parsed location if a valid fix was received,
90 // otherwise leaves it unchanged.
91 bool ParseServerResponse(const std::string& response_body,
92 const base::Time& timestamp,
93 Geoposition* position,
94 string16* access_token);
95 void AddWifiData(const WifiData& wifi_data,
96 int age_milliseconds,
97 base::DictionaryValue* request);
98 } // namespace
100 int NetworkLocationRequest::url_fetcher_id_for_tests = 0;
102 NetworkLocationRequest::NetworkLocationRequest(
103 net::URLRequestContextGetter* context,
104 const GURL& url,
105 LocationResponseCallback callback)
106 : url_context_(context),
107 callback_(callback),
108 url_(url) {
111 NetworkLocationRequest::~NetworkLocationRequest() {
114 bool NetworkLocationRequest::MakeRequest(const string16& access_token,
115 const WifiData& wifi_data,
116 const base::Time& timestamp) {
117 RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_REQUEST_START);
118 RecordUmaAccessPoints(wifi_data.access_point_data.size());
119 if (url_fetcher_ != NULL) {
120 DVLOG(1) << "NetworkLocationRequest : Cancelling pending request";
121 RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_REQUEST_CANCEL);
122 url_fetcher_.reset();
124 wifi_data_ = wifi_data;
125 timestamp_ = timestamp;
127 GURL request_url = FormRequestURL(url_);
128 url_fetcher_.reset(net::URLFetcher::Create(
129 url_fetcher_id_for_tests, request_url, net::URLFetcher::POST, this));
130 url_fetcher_->SetRequestContext(url_context_.get());
131 std::string upload_data;
132 FormUploadData(wifi_data, timestamp, access_token, &upload_data);
133 url_fetcher_->SetUploadData("application/json", upload_data);
134 url_fetcher_->SetLoadFlags(
135 net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
136 net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES |
137 net::LOAD_DO_NOT_SEND_AUTH_DATA);
139 start_time_ = base::TimeTicks::Now();
140 url_fetcher_->Start();
141 return true;
144 void NetworkLocationRequest::OnURLFetchComplete(
145 const net::URLFetcher* source) {
146 DCHECK_EQ(url_fetcher_.get(), source);
148 net::URLRequestStatus status = source->GetStatus();
149 int response_code = source->GetResponseCode();
150 RecordUmaResponseCode(response_code);
152 Geoposition position;
153 string16 access_token;
154 std::string data;
155 source->GetResponseAsString(&data);
156 GetLocationFromResponse(status.is_success(),
157 response_code,
158 data,
159 timestamp_,
160 source->GetURL(),
161 &position,
162 &access_token);
163 const bool server_error =
164 !status.is_success() || (response_code >= 500 && response_code < 600);
165 url_fetcher_.reset();
167 if (!server_error) {
168 const base::TimeDelta request_time = base::TimeTicks::Now() - start_time_;
170 UMA_HISTOGRAM_CUSTOM_TIMES(
171 "Net.Wifi.LbsLatency",
172 request_time,
173 base::TimeDelta::FromMilliseconds(1),
174 base::TimeDelta::FromSeconds(10),
175 100);
178 DVLOG(1) << "NetworkLocationRequest::OnURLFetchComplete() : run callback.";
179 callback_.Run(position, server_error, access_token, wifi_data_);
182 // Local functions.
183 namespace {
185 struct AccessPointLess {
186 bool operator()(const AccessPointData* ap1,
187 const AccessPointData* ap2) const {
188 return ap2->radio_signal_strength < ap1->radio_signal_strength;
192 GURL FormRequestURL(const GURL& url) {
193 if (url == LocationArbitratorImpl::DefaultNetworkProviderURL()) {
194 std::string api_key = google_apis::GetAPIKey();
195 if (!api_key.empty()) {
196 std::string query(url.query());
197 if (!query.empty())
198 query += "&";
199 query += "key=" + net::EscapeQueryParamValue(api_key, true);
200 GURL::Replacements replacements;
201 replacements.SetQueryStr(query);
202 return url.ReplaceComponents(replacements);
205 return url;
208 void FormUploadData(const WifiData& wifi_data,
209 const base::Time& timestamp,
210 const string16& access_token,
211 std::string* upload_data) {
212 int age = kint32min; // Invalid so AddInteger() will ignore.
213 if (!timestamp.is_null()) {
214 // Convert absolute timestamps into a relative age.
215 int64 delta_ms = (base::Time::Now() - timestamp).InMilliseconds();
216 if (delta_ms >= 0 && delta_ms < kint32max)
217 age = static_cast<int>(delta_ms);
220 base::DictionaryValue request;
221 AddWifiData(wifi_data, age, &request);
222 if (!access_token.empty())
223 request.SetString(kAccessTokenString, access_token);
224 base::JSONWriter::Write(&request, upload_data);
227 void AddString(const std::string& property_name, const std::string& value,
228 base::DictionaryValue* dict) {
229 DCHECK(dict);
230 if (!value.empty())
231 dict->SetString(property_name, value);
234 void AddInteger(const std::string& property_name, int value,
235 base::DictionaryValue* dict) {
236 DCHECK(dict);
237 if (value != kint32min)
238 dict->SetInteger(property_name, value);
241 void AddWifiData(const WifiData& wifi_data,
242 int age_milliseconds,
243 base::DictionaryValue* request) {
244 DCHECK(request);
246 if (wifi_data.access_point_data.empty())
247 return;
249 typedef std::multiset<const AccessPointData*, AccessPointLess> AccessPointSet;
250 AccessPointSet access_points_by_signal_strength;
252 for (WifiData::AccessPointDataSet::const_iterator iter =
253 wifi_data.access_point_data.begin();
254 iter != wifi_data.access_point_data.end();
255 ++iter) {
256 access_points_by_signal_strength.insert(&(*iter));
259 base::ListValue* wifi_access_point_list = new base::ListValue();
260 for (AccessPointSet::iterator iter =
261 access_points_by_signal_strength.begin();
262 iter != access_points_by_signal_strength.end();
263 ++iter) {
264 base::DictionaryValue* wifi_dict = new base::DictionaryValue();
265 AddString("macAddress", UTF16ToUTF8((*iter)->mac_address), wifi_dict);
266 AddInteger("signalStrength", (*iter)->radio_signal_strength, wifi_dict);
267 AddInteger("age", age_milliseconds, wifi_dict);
268 AddInteger("channel", (*iter)->channel, wifi_dict);
269 AddInteger("signalToNoiseRatio", (*iter)->signal_to_noise, wifi_dict);
270 wifi_access_point_list->Append(wifi_dict);
272 request->Set("wifiAccessPoints", wifi_access_point_list);
275 void FormatPositionError(const GURL& server_url,
276 const std::string& message,
277 Geoposition* position) {
278 position->error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
279 position->error_message = "Network location provider at '";
280 position->error_message += server_url.GetOrigin().spec();
281 position->error_message += "' : ";
282 position->error_message += message;
283 position->error_message += ".";
284 VLOG(1) << "NetworkLocationRequest::GetLocationFromResponse() : "
285 << position->error_message;
288 void GetLocationFromResponse(bool http_post_result,
289 int status_code,
290 const std::string& response_body,
291 const base::Time& timestamp,
292 const GURL& server_url,
293 Geoposition* position,
294 string16* access_token) {
295 DCHECK(position);
296 DCHECK(access_token);
298 // HttpPost can fail for a number of reasons. Most likely this is because
299 // we're offline, or there was no response.
300 if (!http_post_result) {
301 FormatPositionError(server_url, "No response received", position);
302 RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_EMPTY);
303 return;
305 if (status_code != 200) { // HTTP OK.
306 std::string message = "Returned error code ";
307 message += base::IntToString(status_code);
308 FormatPositionError(server_url, message, position);
309 RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_NOT_OK);
310 return;
312 // We use the timestamp from the wifi data that was used to generate
313 // this position fix.
314 if (!ParseServerResponse(response_body, timestamp, position, access_token)) {
315 // We failed to parse the repsonse.
316 FormatPositionError(server_url, "Response was malformed", position);
317 RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_MALFORMED);
318 return;
320 // The response was successfully parsed, but it may not be a valid
321 // position fix.
322 if (!position->Validate()) {
323 FormatPositionError(server_url,
324 "Did not provide a good position fix", position);
325 RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_INVALID_FIX);
326 return;
328 RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_SUCCESS);
331 // Numeric values without a decimal point have type integer and IsDouble() will
332 // return false. This is convenience function for detecting integer or floating
333 // point numeric values. Note that isIntegral() includes boolean values, which
334 // is not what we want.
335 bool GetAsDouble(const base::DictionaryValue& object,
336 const std::string& property_name,
337 double* out) {
338 DCHECK(out);
339 const base::Value* value = NULL;
340 if (!object.Get(property_name, &value))
341 return false;
342 int value_as_int;
343 DCHECK(value);
344 if (value->GetAsInteger(&value_as_int)) {
345 *out = value_as_int;
346 return true;
348 return value->GetAsDouble(out);
351 bool ParseServerResponse(const std::string& response_body,
352 const base::Time& timestamp,
353 Geoposition* position,
354 string16* access_token) {
355 DCHECK(position);
356 DCHECK(!position->Validate());
357 DCHECK(position->error_code == Geoposition::ERROR_CODE_NONE);
358 DCHECK(access_token);
359 DCHECK(!timestamp.is_null());
361 if (response_body.empty()) {
362 LOG(WARNING) << "ParseServerResponse() : Response was empty.";
363 return false;
365 DVLOG(1) << "ParseServerResponse() : Parsing response " << response_body;
367 // Parse the response, ignoring comments.
368 std::string error_msg;
369 scoped_ptr<base::Value> response_value(base::JSONReader::ReadAndReturnError(
370 response_body, base::JSON_PARSE_RFC, NULL, &error_msg));
371 if (response_value == NULL) {
372 LOG(WARNING) << "ParseServerResponse() : JSONReader failed : "
373 << error_msg;
374 return false;
377 if (!response_value->IsType(base::Value::TYPE_DICTIONARY)) {
378 VLOG(1) << "ParseServerResponse() : Unexpected response type "
379 << response_value->GetType();
380 return false;
382 const base::DictionaryValue* response_object =
383 static_cast<base::DictionaryValue*>(response_value.get());
385 // Get the access token, if any.
386 response_object->GetString(kAccessTokenString, access_token);
388 // Get the location
389 const base::Value* location_value = NULL;
390 if (!response_object->Get(kLocationString, &location_value)) {
391 VLOG(1) << "ParseServerResponse() : Missing location attribute.";
392 // GLS returns a response with no location property to represent
393 // no fix available; return true to indicate successful parse.
394 return true;
396 DCHECK(location_value);
398 if (!location_value->IsType(base::Value::TYPE_DICTIONARY)) {
399 if (!location_value->IsType(base::Value::TYPE_NULL)) {
400 VLOG(1) << "ParseServerResponse() : Unexpected location type "
401 << location_value->GetType();
402 // If the network provider was unable to provide a position fix, it should
403 // return a HTTP 200, with "location" : null. Otherwise it's an error.
404 return false;
406 return true; // Successfully parsed response containing no fix.
408 const base::DictionaryValue* location_object =
409 static_cast<const base::DictionaryValue*>(location_value);
411 // latitude and longitude fields are always required.
412 double latitude, longitude;
413 if (!GetAsDouble(*location_object, kLatitudeString, &latitude) ||
414 !GetAsDouble(*location_object, kLongitudeString, &longitude)) {
415 VLOG(1) << "ParseServerResponse() : location lacks lat and/or long.";
416 return false;
418 // All error paths covered: now start actually modifying postion.
419 position->latitude = latitude;
420 position->longitude = longitude;
421 position->timestamp = timestamp;
423 // Other fields are optional.
424 GetAsDouble(*response_object, kAccuracyString, &position->accuracy);
426 return true;
429 } // namespace
431 } // namespace content