OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "content/browser/geolocation/network_location_request.h" | 5 #include "content/browser/geolocation/network_location_request.h" |
6 | 6 |
7 #include <set> | 7 #include <set> |
8 #include <string> | 8 #include <string> |
9 | 9 |
10 #include "base/json/json_reader.h" | 10 #include "base/json/json_reader.h" |
11 #include "base/json/json_writer.h" | 11 #include "base/json/json_writer.h" |
12 #include "base/string_number_conversions.h" | 12 #include "base/string_number_conversions.h" |
13 #include "base/utf_string_conversions.h" | 13 #include "base/utf_string_conversions.h" |
14 #include "base/values.h" | 14 #include "base/values.h" |
| 15 #include "content/browser/geolocation/location_arbitrator.h" |
15 #include "content/public/common/geoposition.h" | 16 #include "content/public/common/geoposition.h" |
| 17 #include "google_apis/google_api_keys.h" |
16 #include "net/base/escape.h" | 18 #include "net/base/escape.h" |
17 #include "net/base/load_flags.h" | 19 #include "net/base/load_flags.h" |
18 #include "net/url_request/url_fetcher.h" | 20 #include "net/url_request/url_fetcher.h" |
19 #include "net/url_request/url_request_context_getter.h" | 21 #include "net/url_request/url_request_context_getter.h" |
20 #include "net/url_request/url_request_status.h" | 22 #include "net/url_request/url_request_status.h" |
21 | 23 |
22 namespace { | 24 namespace { |
23 | 25 |
24 const size_t kMaxRequestLength = 2048; | 26 const size_t kMaxRequestLength = 2048; |
25 | 27 |
26 const char kAccessTokenString[] = "access_token"; | 28 const char kAccessTokenString[] = "accessToken"; |
27 const char kLocationString[] = "location"; | 29 const char kLocationString[] = "location"; |
28 const char kLatitudeString[] = "lat"; | 30 const char kLatitudeString[] = "lat"; |
29 const char kLongitudeString[] = "lng"; | 31 const char kLongitudeString[] = "lng"; |
30 const char kAccuracyString[] = "accuracy"; | 32 const char kAccuracyString[] = "accuracy"; |
31 const char kStatusString[] = "status"; | 33 const char kStatusString[] = "status"; |
32 const char kStatusOKString[] = "OK"; | 34 const char kStatusOKString[] = "OK"; |
33 | 35 |
34 // Local functions | 36 // Local functions |
35 // Creates the request url to send to the server. | 37 // Creates the request url to send to the server. |
36 GURL FormRequestURL(const std::string& url, | 38 GURL FormRequestURL(const GURL& url); |
| 39 |
| 40 void FormUploadData(const WifiData& wifi_data, |
| 41 const base::Time& timestamp, |
37 const string16& access_token, | 42 const string16& access_token, |
38 const WifiData& wifi_data, | 43 std::string* upload_data); |
39 const base::Time& timestamp); | |
40 | 44 |
41 // Parsers the server response. | 45 // Parsers the server response. |
42 void GetLocationFromResponse(bool http_post_result, | 46 void GetLocationFromResponse(bool http_post_result, |
43 int status_code, | 47 int status_code, |
44 const std::string& response_body, | 48 const std::string& response_body, |
45 const base::Time& timestamp, | 49 const base::Time& timestamp, |
46 const GURL& server_url, | 50 const GURL& server_url, |
47 content::Geoposition* position, | 51 content::Geoposition* position, |
48 string16* access_token); | 52 string16* access_token); |
49 | 53 |
50 // Parses the server response body. Returns true if parsing was successful. | 54 // Parses the server response body. Returns true if parsing was successful. |
51 // Sets |*position| to the parsed location if a valid fix was received, | 55 // Sets |*position| to the parsed location if a valid fix was received, |
52 // otherwise leaves it unchanged. | 56 // otherwise leaves it unchanged. |
53 bool ParseServerResponse(const std::string& response_body, | 57 bool ParseServerResponse(const std::string& response_body, |
54 const base::Time& timestamp, | 58 const base::Time& timestamp, |
55 content::Geoposition* position, | 59 content::Geoposition* position, |
56 string16* access_token); | 60 string16* access_token); |
57 void AddWifiData(const WifiData& wifi_data, | 61 void AddWifiData(const WifiData& wifi_data, |
58 int age_milliseconds, | 62 int age_milliseconds, |
59 std::vector<std::string>* params); | 63 base::DictionaryValue* request); |
60 } // namespace | 64 } // namespace |
61 | 65 |
62 int NetworkLocationRequest::url_fetcher_id_for_tests = 0; | 66 int NetworkLocationRequest::url_fetcher_id_for_tests = 0; |
63 | 67 |
64 NetworkLocationRequest::NetworkLocationRequest( | 68 NetworkLocationRequest::NetworkLocationRequest( |
65 net::URLRequestContextGetter* context, | 69 net::URLRequestContextGetter* context, |
66 const GURL& url, | 70 const GURL& url, |
67 ListenerInterface* listener) | 71 ListenerInterface* listener) |
68 : url_context_(context), listener_(listener), | 72 : url_context_(context), listener_(listener), |
69 url_(url) { | 73 url_(url) { |
70 DCHECK(listener); | 74 DCHECK(listener); |
71 } | 75 } |
72 | 76 |
73 NetworkLocationRequest::~NetworkLocationRequest() { | 77 NetworkLocationRequest::~NetworkLocationRequest() { |
74 } | 78 } |
75 | 79 |
76 bool NetworkLocationRequest::MakeRequest(const string16& access_token, | 80 bool NetworkLocationRequest::MakeRequest(const string16& access_token, |
77 const WifiData& wifi_data, | 81 const WifiData& wifi_data, |
78 const base::Time& timestamp) { | 82 const base::Time& timestamp) { |
79 if (url_fetcher_ != NULL) { | 83 if (url_fetcher_ != NULL) { |
80 DVLOG(1) << "NetworkLocationRequest : Cancelling pending request"; | 84 DVLOG(1) << "NetworkLocationRequest : Cancelling pending request"; |
81 url_fetcher_.reset(); | 85 url_fetcher_.reset(); |
82 } | 86 } |
83 wifi_data_ = wifi_data; | 87 wifi_data_ = wifi_data; |
84 timestamp_ = timestamp; | 88 timestamp_ = timestamp; |
85 | 89 |
86 GURL request_url = FormRequestURL(url_.spec(), access_token, | 90 GURL request_url = FormRequestURL(url_); |
87 wifi_data, timestamp_); | |
88 url_fetcher_.reset(net::URLFetcher::Create( | 91 url_fetcher_.reset(net::URLFetcher::Create( |
89 url_fetcher_id_for_tests, request_url, net::URLFetcher::GET, this)); | 92 url_fetcher_id_for_tests, request_url, net::URLFetcher::POST, this)); |
90 url_fetcher_->SetRequestContext(url_context_); | 93 url_fetcher_->SetRequestContext(url_context_); |
| 94 std::string upload_data; |
| 95 FormUploadData(wifi_data, timestamp, access_token, &upload_data); |
| 96 url_fetcher_->SetUploadData("application/json", upload_data); |
91 url_fetcher_->SetLoadFlags( | 97 url_fetcher_->SetLoadFlags( |
92 net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE | | 98 net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE | |
93 net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES | | 99 net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES | |
94 net::LOAD_DO_NOT_SEND_AUTH_DATA); | 100 net::LOAD_DO_NOT_SEND_AUTH_DATA); |
95 | 101 |
96 url_fetcher_->Start(); | 102 url_fetcher_->Start(); |
97 return true; | 103 return true; |
98 } | 104 } |
99 | 105 |
100 void NetworkLocationRequest::OnURLFetchComplete( | 106 void NetworkLocationRequest::OnURLFetchComplete( |
(...skipping 27 matching lines...) Expand all Loading... |
128 // Local functions. | 134 // Local functions. |
129 namespace { | 135 namespace { |
130 | 136 |
131 struct AccessPointLess { | 137 struct AccessPointLess { |
132 bool operator()(const AccessPointData* ap1, | 138 bool operator()(const AccessPointData* ap1, |
133 const AccessPointData* ap2) const { | 139 const AccessPointData* ap2) const { |
134 return ap2->radio_signal_strength < ap1->radio_signal_strength; | 140 return ap2->radio_signal_strength < ap1->radio_signal_strength; |
135 } | 141 } |
136 }; | 142 }; |
137 | 143 |
138 GURL FormRequestURL(const std::string& url, | 144 GURL FormRequestURL(const GURL& url) { |
| 145 if (url == GeolocationArbitrator::DefaultNetworkProviderURL()) { |
| 146 std::string api_key = google_apis::GetAPIKey(); |
| 147 if (!api_key.empty()) { |
| 148 std::string query(url.query()); |
| 149 if (!query.empty()) |
| 150 query += "&"; |
| 151 query += "key=" + net::EscapeQueryParamValue(api_key, true); |
| 152 GURL::Replacements replacements; |
| 153 replacements.SetQueryStr(query); |
| 154 return url.ReplaceComponents(replacements); |
| 155 } |
| 156 } |
| 157 return url; |
| 158 } |
| 159 |
| 160 void FormUploadData(const WifiData& wifi_data, |
| 161 const base::Time& timestamp, |
139 const string16& access_token, | 162 const string16& access_token, |
140 const WifiData& wifi_data, | 163 std::string* upload_data) { |
141 const base::Time& timestamp) { | |
142 int age = kint32min; // Invalid so AddInteger() will ignore. | 164 int age = kint32min; // Invalid so AddInteger() will ignore. |
143 if (!timestamp.is_null()) { | 165 if (!timestamp.is_null()) { |
144 // Convert absolute timestamps into a relative age. | 166 // Convert absolute timestamps into a relative age. |
145 int64 delta_ms = (base::Time::Now() - timestamp).InMilliseconds(); | 167 int64 delta_ms = (base::Time::Now() - timestamp).InMilliseconds(); |
146 if (delta_ms >= 0 && delta_ms < kint32max) | 168 if (delta_ms >= 0 && delta_ms < kint32max) |
147 age = static_cast<int>(delta_ms); | 169 age = static_cast<int>(delta_ms); |
148 } | 170 } |
149 | 171 |
150 std::vector<std::string> params; | 172 base::DictionaryValue request; |
151 #if defined(GOOGLE_CHROME_BUILD) | 173 AddWifiData(wifi_data, age, &request); |
152 params.push_back("browser=googlechrome"); | |
153 #else | |
154 params.push_back("browser=chromium"); | |
155 #endif | |
156 | |
157 params.push_back("sensor=true"); | |
158 if (!access_token.empty()) | 174 if (!access_token.empty()) |
159 params.push_back("token=" + UTF16ToUTF8(access_token)); | 175 request.SetString(kAccessTokenString, access_token); |
160 AddWifiData(wifi_data, age, ¶ms); | 176 base::JSONWriter::Write(&request, upload_data); |
161 | |
162 std::string request_string = url + '?' + JoinString(params, '&'); | |
163 if (request_string.length() > kMaxRequestLength) { | |
164 size_t last_param_pos = | |
165 request_string.find_last_of('&', kMaxRequestLength); | |
166 CHECK_NE(std::string::npos, last_param_pos); | |
167 request_string.erase(last_param_pos); | |
168 } | |
169 | |
170 return GURL(request_string); | |
171 } | 177 } |
172 | 178 |
173 void AddString(const std::string& property_name, const std::string& value, | 179 void AddString(const std::string& property_name, const std::string& value, |
174 std::string* wifi_params) { | 180 base::DictionaryValue* dict) { |
175 DCHECK(wifi_params); | 181 DCHECK(dict); |
176 if (!value.empty()) { | 182 if (!value.empty()) |
177 if (!wifi_params->empty()) | 183 dict->SetString(property_name, value); |
178 *wifi_params += '|'; | |
179 *wifi_params += property_name; | |
180 *wifi_params += value; | |
181 } | |
182 } | 184 } |
183 | 185 |
184 void AddInteger(const std::string& property_name, int value, | 186 void AddInteger(const std::string& property_name, int value, |
185 std::string* wifi_params) { | 187 base::DictionaryValue* dict) { |
186 DCHECK(wifi_params); | 188 DCHECK(dict); |
187 if (value != kint32min) { | 189 if (value != kint32min) |
188 if (!wifi_params->empty()) | 190 dict->SetInteger(property_name, value); |
189 *wifi_params += '|'; | |
190 *wifi_params += property_name; | |
191 *wifi_params += base::IntToString(value); | |
192 } | |
193 } | 191 } |
194 | 192 |
195 void AddWifiData(const WifiData& wifi_data, | 193 void AddWifiData(const WifiData& wifi_data, |
196 int age_milliseconds, | 194 int age_milliseconds, |
197 std::vector<std::string>* params) { | 195 base::DictionaryValue* request) { |
198 DCHECK(params); | 196 DCHECK(request); |
199 | 197 |
200 if (wifi_data.access_point_data.empty()) | 198 if (wifi_data.access_point_data.empty()) |
201 return; | 199 return; |
202 | 200 |
203 typedef std::multiset<const AccessPointData*, AccessPointLess> AccessPointSet; | 201 typedef std::multiset<const AccessPointData*, AccessPointLess> AccessPointSet; |
204 AccessPointSet access_points_by_signal_strength; | 202 AccessPointSet access_points_by_signal_strength; |
205 | 203 |
206 for (WifiData::AccessPointDataSet::const_iterator iter = | 204 for (WifiData::AccessPointDataSet::const_iterator iter = |
207 wifi_data.access_point_data.begin(); | 205 wifi_data.access_point_data.begin(); |
208 iter != wifi_data.access_point_data.end(); | 206 iter != wifi_data.access_point_data.end(); |
209 ++iter) { | 207 ++iter) { |
210 access_points_by_signal_strength.insert(&(*iter)); | 208 access_points_by_signal_strength.insert(&(*iter)); |
211 } | 209 } |
212 | 210 |
| 211 base::ListValue* wifi_access_point_list = new base::ListValue(); |
213 for (AccessPointSet::iterator iter = | 212 for (AccessPointSet::iterator iter = |
214 access_points_by_signal_strength.begin(); | 213 access_points_by_signal_strength.begin(); |
215 iter != access_points_by_signal_strength.end(); | 214 iter != access_points_by_signal_strength.end(); |
216 ++iter) { | 215 ++iter) { |
217 std::string wifi_params; | 216 base::DictionaryValue* wifi_dict = new base::DictionaryValue(); |
218 AddString("mac:", UTF16ToUTF8((*iter)->mac_address), &wifi_params); | 217 AddString("macAddress", UTF16ToUTF8((*iter)->mac_address), wifi_dict); |
219 AddInteger("ss:", (*iter)->radio_signal_strength, &wifi_params); | 218 AddInteger("signalStrength", (*iter)->radio_signal_strength, wifi_dict); |
220 AddInteger("age:", age_milliseconds, &wifi_params); | 219 AddInteger("age", age_milliseconds, wifi_dict); |
221 AddInteger("chan:", (*iter)->channel, &wifi_params); | 220 AddInteger("channel", (*iter)->channel, wifi_dict); |
222 AddInteger("snr:", (*iter)->signal_to_noise, &wifi_params); | 221 AddInteger("signalToNoiseRatio", (*iter)->signal_to_noise, wifi_dict); |
223 std::string ssid = UTF16ToUTF8((*iter)->ssid); | 222 wifi_access_point_list->Append(wifi_dict); |
224 // Backslash characters in the ssid need backslash-escaping to avoid | |
225 // escaping a following wifi parameter separator. | |
226 ReplaceSubstringsAfterOffset(&ssid, 0, "\\", "\\\\"); | |
227 // Pipe characters in the ssid need backslash-escaping to avoid being | |
228 // interpreted as the wifi parameter separator. | |
229 ReplaceSubstringsAfterOffset(&ssid, 0, "|", "\\|"); | |
230 AddString("ssid:", ssid, &wifi_params); | |
231 params->push_back( | |
232 "wifi=" + net::EscapeQueryParamValue(wifi_params, false)); | |
233 } | 223 } |
| 224 request->Set("wifiAccessPoints", wifi_access_point_list); |
234 } | 225 } |
235 | 226 |
236 void FormatPositionError(const GURL& server_url, | 227 void FormatPositionError(const GURL& server_url, |
237 const std::string& message, | 228 const std::string& message, |
238 content::Geoposition* position) { | 229 content::Geoposition* position) { |
239 position->error_code = | 230 position->error_code = |
240 content::Geoposition::ERROR_CODE_POSITION_UNAVAILABLE; | 231 content::Geoposition::ERROR_CODE_POSITION_UNAVAILABLE; |
241 position->error_message = "Network location provider at '"; | 232 position->error_message = "Network location provider at '"; |
242 position->error_message += server_url.possibly_invalid_spec(); | 233 position->error_message += server_url.GetOrigin().spec(); |
243 position->error_message += "' : "; | 234 position->error_message += "' : "; |
244 position->error_message += message; | 235 position->error_message += message; |
245 position->error_message += "."; | 236 position->error_message += "."; |
246 VLOG(1) << "NetworkLocationRequest::GetLocationFromResponse() : " | 237 VLOG(1) << "NetworkLocationRequest::GetLocationFromResponse() : " |
247 << position->error_message; | 238 << position->error_message; |
248 } | 239 } |
249 | 240 |
250 void GetLocationFromResponse(bool http_post_result, | 241 void GetLocationFromResponse(bool http_post_result, |
251 int status_code, | 242 int status_code, |
252 const std::string& response_body, | 243 const std::string& response_body, |
(...skipping 29 matching lines...) Expand all Loading... |
282 FormatPositionError(server_url, | 273 FormatPositionError(server_url, |
283 "Did not provide a good position fix", position); | 274 "Did not provide a good position fix", position); |
284 return; | 275 return; |
285 } | 276 } |
286 } | 277 } |
287 | 278 |
288 // Numeric values without a decimal point have type integer and IsDouble() will | 279 // Numeric values without a decimal point have type integer and IsDouble() will |
289 // return false. This is convenience function for detecting integer or floating | 280 // return false. This is convenience function for detecting integer or floating |
290 // point numeric values. Note that isIntegral() includes boolean values, which | 281 // point numeric values. Note that isIntegral() includes boolean values, which |
291 // is not what we want. | 282 // is not what we want. |
292 bool GetAsDouble(const DictionaryValue& object, | 283 bool GetAsDouble(const base::DictionaryValue& object, |
293 const std::string& property_name, | 284 const std::string& property_name, |
294 double* out) { | 285 double* out) { |
295 DCHECK(out); | 286 DCHECK(out); |
296 const Value* value = NULL; | 287 const Value* value = NULL; |
297 if (!object.Get(property_name, &value)) | 288 if (!object.Get(property_name, &value)) |
298 return false; | 289 return false; |
299 int value_as_int; | 290 int value_as_int; |
300 DCHECK(value); | 291 DCHECK(value); |
301 if (value->GetAsInteger(&value_as_int)) { | 292 if (value->GetAsInteger(&value_as_int)) { |
302 *out = value_as_int; | 293 *out = value_as_int; |
(...skipping 26 matching lines...) Expand all Loading... |
329 LOG(WARNING) << "ParseServerResponse() : JSONReader failed : " | 320 LOG(WARNING) << "ParseServerResponse() : JSONReader failed : " |
330 << error_msg; | 321 << error_msg; |
331 return false; | 322 return false; |
332 } | 323 } |
333 | 324 |
334 if (!response_value->IsType(Value::TYPE_DICTIONARY)) { | 325 if (!response_value->IsType(Value::TYPE_DICTIONARY)) { |
335 VLOG(1) << "ParseServerResponse() : Unexpected response type " | 326 VLOG(1) << "ParseServerResponse() : Unexpected response type " |
336 << response_value->GetType(); | 327 << response_value->GetType(); |
337 return false; | 328 return false; |
338 } | 329 } |
339 const DictionaryValue* response_object = | 330 const base::DictionaryValue* response_object = |
340 static_cast<DictionaryValue*>(response_value.get()); | 331 static_cast<base::DictionaryValue*>(response_value.get()); |
341 | |
342 // Check the status code. | |
343 const Value* status_value = NULL; | |
344 if (!response_object->Get(kStatusString, &status_value)) { | |
345 VLOG(1) << "ParseServerResponse() : Missing status attribute."; | |
346 // The status attribute is required. | |
347 return false; | |
348 } | |
349 DCHECK(status_value); | |
350 | |
351 if (!status_value->IsType(Value::TYPE_STRING)) { | |
352 VLOG(1) << "ParseServerResponse() : Unexpected status type " | |
353 << status_value->GetType(); | |
354 // The status attribute is required to be a string. | |
355 return false; | |
356 } | |
357 const StringValue* status_object = | |
358 static_cast<const StringValue*>(status_value); | |
359 | |
360 std::string status; | |
361 if (!status_object->GetAsString(&status)) { | |
362 VLOG(1) << "ParseServerResponse() : Error parsing the status value."; | |
363 return false; | |
364 } | |
365 | |
366 if (status != kStatusOKString) { | |
367 VLOG(1) << "ParseServerResponse() : Request failed with status " | |
368 << status; | |
369 return false; | |
370 } | |
371 | 332 |
372 // Get the access token, if any. | 333 // Get the access token, if any. |
373 response_object->GetString(kAccessTokenString, access_token); | 334 response_object->GetString(kAccessTokenString, access_token); |
374 | 335 |
375 // Get the location | 336 // Get the location |
376 const Value* location_value = NULL; | 337 const Value* location_value = NULL; |
377 if (!response_object->Get(kLocationString, &location_value)) { | 338 if (!response_object->Get(kLocationString, &location_value)) { |
378 VLOG(1) << "ParseServerResponse() : Missing location attribute."; | 339 VLOG(1) << "ParseServerResponse() : Missing location attribute."; |
379 // GLS returns a response with no location property to represent | 340 // GLS returns a response with no location property to represent |
380 // no fix available; return true to indicate successful parse. | 341 // no fix available; return true to indicate successful parse. |
381 return true; | 342 return true; |
382 } | 343 } |
383 DCHECK(location_value); | 344 DCHECK(location_value); |
384 | 345 |
385 if (!location_value->IsType(Value::TYPE_DICTIONARY)) { | 346 if (!location_value->IsType(Value::TYPE_DICTIONARY)) { |
386 if (!location_value->IsType(Value::TYPE_NULL)) { | 347 if (!location_value->IsType(Value::TYPE_NULL)) { |
387 VLOG(1) << "ParseServerResponse() : Unexpected location type " | 348 VLOG(1) << "ParseServerResponse() : Unexpected location type " |
388 << location_value->GetType(); | 349 << location_value->GetType(); |
389 // If the network provider was unable to provide a position fix, it should | 350 // If the network provider was unable to provide a position fix, it should |
390 // return a HTTP 200, with "location" : null. Otherwise it's an error. | 351 // return a HTTP 200, with "location" : null. Otherwise it's an error. |
391 return false; | 352 return false; |
392 } | 353 } |
393 return true; // Successfully parsed response containing no fix. | 354 return true; // Successfully parsed response containing no fix. |
394 } | 355 } |
395 const DictionaryValue* location_object = | 356 const base::DictionaryValue* location_object = |
396 static_cast<const DictionaryValue*>(location_value); | 357 static_cast<const base::DictionaryValue*>(location_value); |
397 | 358 |
398 // latitude and longitude fields are always required. | 359 // latitude and longitude fields are always required. |
399 double latitude, longitude; | 360 double latitude, longitude; |
400 if (!GetAsDouble(*location_object, kLatitudeString, &latitude) || | 361 if (!GetAsDouble(*location_object, kLatitudeString, &latitude) || |
401 !GetAsDouble(*location_object, kLongitudeString, &longitude)) { | 362 !GetAsDouble(*location_object, kLongitudeString, &longitude)) { |
402 VLOG(1) << "ParseServerResponse() : location lacks lat and/or long."; | 363 VLOG(1) << "ParseServerResponse() : location lacks lat and/or long."; |
403 return false; | 364 return false; |
404 } | 365 } |
405 // All error paths covered: now start actually modifying postion. | 366 // All error paths covered: now start actually modifying postion. |
406 position->latitude = latitude; | 367 position->latitude = latitude; |
407 position->longitude = longitude; | 368 position->longitude = longitude; |
408 position->timestamp = timestamp; | 369 position->timestamp = timestamp; |
409 | 370 |
410 // Other fields are optional. | 371 // Other fields are optional. |
411 GetAsDouble(*response_object, kAccuracyString, &position->accuracy); | 372 GetAsDouble(*response_object, kAccuracyString, &position->accuracy); |
412 | 373 |
413 return true; | 374 return true; |
414 } | 375 } |
415 | 376 |
416 } // namespace | 377 } // namespace |
| 378 |
OLD | NEW |