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"
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"
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",
61 void RecordUmaAccessPoints(int count
) {
64 const int buckets
= 21;
65 UMA_HISTOGRAM_CUSTOM_COUNTS("Geolocation.NetworkLocationRequest.AccessPoints",
66 count
, min
, max
, buckets
);
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 base::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
,
82 const std::string
& response_body
,
83 const base::Time
& timestamp
,
84 const GURL
& server_url
,
85 Geoposition
* position
,
86 base::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 base::string16
* access_token
);
95 void AddWifiData(const WifiData
& wifi_data
,
97 base::DictionaryValue
* request
);
100 int NetworkLocationRequest::url_fetcher_id_for_tests
= 0;
102 NetworkLocationRequest::NetworkLocationRequest(
103 net::URLRequestContextGetter
* context
,
105 LocationResponseCallback callback
)
106 : url_context_(context
), location_response_callback_(callback
), url_(url
) {
109 NetworkLocationRequest::~NetworkLocationRequest() {
112 bool NetworkLocationRequest::MakeRequest(const base::string16
& access_token
,
113 const WifiData
& wifi_data
,
114 const base::Time
& timestamp
) {
115 RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_REQUEST_START
);
116 RecordUmaAccessPoints(wifi_data
.access_point_data
.size());
117 if (url_fetcher_
!= NULL
) {
118 DVLOG(1) << "NetworkLocationRequest : Cancelling pending request";
119 RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_REQUEST_CANCEL
);
120 url_fetcher_
.reset();
122 wifi_data_
= wifi_data
;
123 wifi_data_timestamp_
= timestamp
;
125 GURL request_url
= FormRequestURL(url_
);
126 url_fetcher_
.reset(net::URLFetcher::Create(
127 url_fetcher_id_for_tests
, request_url
, net::URLFetcher::POST
, this));
128 url_fetcher_
->SetRequestContext(url_context_
.get());
129 std::string upload_data
;
130 FormUploadData(wifi_data
, timestamp
, access_token
, &upload_data
);
131 url_fetcher_
->SetUploadData("application/json", upload_data
);
132 url_fetcher_
->SetLoadFlags(
133 net::LOAD_BYPASS_CACHE
| net::LOAD_DISABLE_CACHE
|
134 net::LOAD_DO_NOT_SAVE_COOKIES
| net::LOAD_DO_NOT_SEND_COOKIES
|
135 net::LOAD_DO_NOT_SEND_AUTH_DATA
);
137 request_start_time_
= base::TimeTicks::Now();
138 url_fetcher_
->Start();
142 void NetworkLocationRequest::OnURLFetchComplete(
143 const net::URLFetcher
* source
) {
144 DCHECK_EQ(url_fetcher_
.get(), source
);
146 net::URLRequestStatus status
= source
->GetStatus();
147 int response_code
= source
->GetResponseCode();
148 RecordUmaResponseCode(response_code
);
150 Geoposition position
;
151 base::string16 access_token
;
153 source
->GetResponseAsString(&data
);
154 GetLocationFromResponse(status
.is_success(),
157 wifi_data_timestamp_
,
161 const bool server_error
=
162 !status
.is_success() || (response_code
>= 500 && response_code
< 600);
163 url_fetcher_
.reset();
166 const base::TimeDelta request_time
=
167 base::TimeTicks::Now() - request_start_time_
;
169 UMA_HISTOGRAM_CUSTOM_TIMES(
170 "Net.Wifi.LbsLatency",
172 base::TimeDelta::FromMilliseconds(1),
173 base::TimeDelta::FromSeconds(10),
177 DVLOG(1) << "NetworkLocationRequest::OnURLFetchComplete() : run callback.";
178 location_response_callback_
.Run(
179 position
, server_error
, access_token
, wifi_data_
);
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());
199 query
+= "key=" + net::EscapeQueryParamValue(api_key
, true);
200 GURL::Replacements replacements
;
201 replacements
.SetQueryStr(query
);
202 return url
.ReplaceComponents(replacements
);
208 void FormUploadData(const WifiData
& wifi_data
,
209 const base::Time
& timestamp
,
210 const base::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
) {
231 dict
->SetString(property_name
, value
);
234 void AddInteger(const std::string
& property_name
, int value
,
235 base::DictionaryValue
* 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
) {
246 if (wifi_data
.access_point_data
.empty())
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();
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();
264 base::DictionaryValue
* wifi_dict
= new base::DictionaryValue();
265 AddString("macAddress", base::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
,
290 const std::string
& response_body
,
291 const base::Time
& timestamp
,
292 const GURL
& server_url
,
293 Geoposition
* position
,
294 base::string16
* access_token
) {
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
);
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
);
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
);
320 // The response was successfully parsed, but it may not be a valid
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
);
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
,
339 const base::Value
* value
= NULL
;
340 if (!object
.Get(property_name
, &value
))
344 if (value
->GetAsInteger(&value_as_int
)) {
348 return value
->GetAsDouble(out
);
351 bool ParseServerResponse(const std::string
& response_body
,
352 const base::Time
& timestamp
,
353 Geoposition
* position
,
354 base::string16
* access_token
) {
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.";
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 : "
377 if (!response_value
->IsType(base::Value::TYPE_DICTIONARY
)) {
378 VLOG(1) << "ParseServerResponse() : Unexpected response type "
379 << response_value
->GetType();
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
);
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.
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.
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.
413 double longitude
= 0;
414 if (!GetAsDouble(*location_object
, kLatitudeString
, &latitude
) ||
415 !GetAsDouble(*location_object
, kLongitudeString
, &longitude
)) {
416 VLOG(1) << "ParseServerResponse() : location lacks lat and/or long.";
419 // All error paths covered: now start actually modifying postion.
420 position
->latitude
= latitude
;
421 position
->longitude
= longitude
;
422 position
->timestamp
= timestamp
;
424 // Other fields are optional.
425 GetAsDouble(*response_object
, kAccuracyString
, &position
->accuracy
);
432 } // namespace content