1 // Copyright (c) 2013 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 "chromeos/network/geolocation_handler.h"
6 
7 #include <stddef.h>
8 #include <stdint.h>
9 
10 #include "base/bind.h"
11 #include "base/logging.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/values.h"
14 #include "chromeos/dbus/shill/shill_manager_client.h"
15 #include "third_party/cros_system_api/dbus/service_constants.h"
16 
17 namespace chromeos {
18 
19 namespace {
20 
21 constexpr const char* kDevicePropertyNames[] = {
22     shill::kGeoWifiAccessPointsProperty, shill::kGeoCellTowersProperty};
23 
HexToDecimal(std::string hex_str)24 std::string HexToDecimal(std::string hex_str) {
25   return std::to_string(std::stoi(hex_str, nullptr, 16));
26 }
27 
FindStringOrEmpty(const base::Value & dict,const base::StringPiece key)28 std::string FindStringOrEmpty(const base::Value& dict,
29                               const base::StringPiece key) {
30   const std::string* val = dict.FindStringKey(key);
31   return val ? *val : std::string();
32 }
33 
34 }  // namespace
35 
GeolocationHandler()36 GeolocationHandler::GeolocationHandler()
37     : cellular_enabled_(false), wifi_enabled_(false) {}
38 
~GeolocationHandler()39 GeolocationHandler::~GeolocationHandler() {
40   if (ShillManagerClient::Get())
41     ShillManagerClient::Get()->RemovePropertyChangedObserver(this);
42 }
43 
Init()44 void GeolocationHandler::Init() {
45   ShillManagerClient::Get()->GetProperties(
46       base::BindOnce(&GeolocationHandler::ManagerPropertiesCallback,
47                      weak_ptr_factory_.GetWeakPtr()));
48   ShillManagerClient::Get()->AddPropertyChangedObserver(this);
49 }
50 
GetWifiAccessPoints(WifiAccessPointVector * access_points,int64_t * age_ms)51 bool GeolocationHandler::GetWifiAccessPoints(
52     WifiAccessPointVector* access_points,
53     int64_t* age_ms) {
54   if (!wifi_enabled_)
55     return false;
56   // Always request updated info.
57   RequestGeolocationObjects();
58   // If no data has been received, return false.
59   if (geolocation_received_time_.is_null() || wifi_access_points_.size() == 0)
60     return false;
61   if (access_points)
62     *access_points = wifi_access_points_;
63   if (age_ms) {
64     base::TimeDelta dtime = base::Time::Now() - geolocation_received_time_;
65     *age_ms = dtime.InMilliseconds();
66   }
67   return true;
68 }
69 
GetNetworkInformation(WifiAccessPointVector * access_points,CellTowerVector * cell_towers)70 bool GeolocationHandler::GetNetworkInformation(
71     WifiAccessPointVector* access_points,
72     CellTowerVector* cell_towers) {
73   if (!cellular_enabled_ && !wifi_enabled_)
74     return false;
75 
76   // Always request updated info.
77   RequestGeolocationObjects();
78 
79   // If no data has been received, return false.
80   if (geolocation_received_time_.is_null())
81     return false;
82 
83   if (cell_towers)
84     *cell_towers = cell_towers_;
85   if (access_points)
86     *access_points = wifi_access_points_;
87 
88   return true;
89 }
90 
OnPropertyChanged(const std::string & key,const base::Value & value)91 void GeolocationHandler::OnPropertyChanged(const std::string& key,
92                                            const base::Value& value) {
93   HandlePropertyChanged(key, value);
94 }
95 
96 //------------------------------------------------------------------------------
97 // Private methods
98 
ManagerPropertiesCallback(base::Optional<base::Value> properties)99 void GeolocationHandler::ManagerPropertiesCallback(
100     base::Optional<base::Value> properties) {
101   if (!properties)
102     return;
103 
104   const base::Value* value =
105       properties->FindKey(shill::kEnabledTechnologiesProperty);
106   if (value)
107     HandlePropertyChanged(shill::kEnabledTechnologiesProperty, *value);
108 }
109 
HandlePropertyChanged(const std::string & key,const base::Value & value)110 void GeolocationHandler::HandlePropertyChanged(const std::string& key,
111                                                const base::Value& value) {
112   if (key != shill::kEnabledTechnologiesProperty)
113     return;
114   const base::ListValue* technologies = nullptr;
115   if (!value.GetAsList(&technologies) || !technologies)
116     return;
117   bool wifi_was_enabled = wifi_enabled_;
118   bool cellular_was_enabled = cellular_enabled_;
119   cellular_enabled_ = false;
120   wifi_enabled_ = false;
121   for (base::ListValue::const_iterator iter = technologies->begin();
122        iter != technologies->end(); ++iter) {
123     std::string technology;
124     iter->GetAsString(&technology);
125     if (technology == shill::kTypeWifi) {
126       wifi_enabled_ = true;
127     } else if (technology == shill::kTypeCellular) {
128       cellular_enabled_ = true;
129     }
130     if (wifi_enabled_ && cellular_enabled_)
131       break;
132   }
133 
134   // Request initial location data.
135   if ((!wifi_was_enabled && wifi_enabled_) ||
136       (!cellular_was_enabled && cellular_enabled_)) {
137     RequestGeolocationObjects();
138   }
139 }
140 
RequestGeolocationObjects()141 void GeolocationHandler::RequestGeolocationObjects() {
142   ShillManagerClient::Get()->GetNetworksForGeolocation(
143       base::BindOnce(&GeolocationHandler::GeolocationCallback,
144                      weak_ptr_factory_.GetWeakPtr()));
145 }
146 
GeolocationCallback(base::Optional<base::Value> properties)147 void GeolocationHandler::GeolocationCallback(
148     base::Optional<base::Value> properties) {
149   if (!properties) {
150     LOG(ERROR) << "Failed to get Geolocation data";
151     return;
152   }
153   wifi_access_points_.clear();
154   cell_towers_.clear();
155   if (properties->DictEmpty())
156     return;  // No enabled devices, don't update received time.
157 
158   // Dictionary<device_type, entry_list>
159   // Example dict returned from shill:
160   // {
161   //   kGeoWifiAccessPointsProperty: [ {kGeoMacAddressProperty: mac_value, ...},
162   //                                   ...
163   //                                 ],
164   //   kGeoCellTowersProperty: [ {kGeoCellIdProperty: cell_id_value, ...}, ... ]
165   // }
166   for (auto* device_type : kDevicePropertyNames) {
167     const base::Value* entry_list = properties->FindKey(device_type);
168     if (!entry_list) {
169       continue;
170     }
171 
172     if (!entry_list->is_list()) {
173       LOG(WARNING) << "Geolocation dictionary value not a List: "
174                    << device_type;
175       continue;
176     }
177 
178     // List[Dictionary<key, value_str>]
179     for (const auto& entry : entry_list->GetList()) {
180       if (!entry.is_dict()) {
181         LOG(WARNING) << "Geolocation list value not a Dictionary";
182         continue;
183       }
184       if (device_type == shill::kGeoWifiAccessPointsProperty) {
185         AddAccessPointFromDict(entry);
186       } else if (device_type == shill::kGeoCellTowersProperty) {
187         AddCellTowerFromDict(entry);
188       }
189     }
190   }
191   geolocation_received_time_ = base::Time::Now();
192 }
193 
AddAccessPointFromDict(const base::Value & entry)194 void GeolocationHandler::AddAccessPointFromDict(const base::Value& entry) {
195   // Docs: developers.google.com/maps/documentation/business/geolocation
196   WifiAccessPoint wap;
197 
198   const std::string* age_str = entry.FindStringKey(shill::kGeoAgeProperty);
199   if (age_str) {
200     int64_t age_ms;
201     if (base::StringToInt64(*age_str, &age_ms)) {
202       wap.timestamp =
203           base::Time::Now() - base::TimeDelta::FromMilliseconds(age_ms);
204     }
205   }
206 
207   wap.mac_address = FindStringOrEmpty(entry, shill::kGeoMacAddressProperty);
208 
209   const std::string* strength_str =
210       entry.FindStringKey(shill::kGeoSignalStrengthProperty);
211   if (strength_str) {
212     base::StringToInt(*strength_str, &wap.signal_strength);
213   }
214 
215   const std::string* signal_str =
216       entry.FindStringKey(shill::kGeoSignalToNoiseRatioProperty);
217   if (signal_str) {
218     base::StringToInt(*signal_str, &wap.signal_to_noise);
219   }
220 
221   const std::string* channel_str =
222       entry.FindStringKey(shill::kGeoChannelProperty);
223   if (channel_str) {
224     base::StringToInt(*channel_str, &wap.channel);
225   }
226 
227   wifi_access_points_.push_back(wap);
228 }
229 
AddCellTowerFromDict(const base::Value & entry)230 void GeolocationHandler::AddCellTowerFromDict(const base::Value& entry) {
231   // Docs: developers.google.com/maps/documentation/business/geolocation
232 
233   // Create object.
234   CellTower ct;
235 
236   // Read time fields into object.
237   const std::string* age_str = entry.FindStringKey(shill::kGeoAgeProperty);
238   if (age_str) {
239     int64_t age_ms;
240     if (base::StringToInt64(*age_str, &age_ms)) {
241       ct.timestamp =
242           base::Time::Now() - base::TimeDelta::FromMilliseconds(age_ms);
243     }
244   }
245 
246   // Read hex fields into object.
247   const std::string* hex_cell_id =
248       entry.FindStringKey(shill::kGeoCellIdProperty);
249   if (hex_cell_id) {
250     ct.ci = HexToDecimal(*hex_cell_id);
251   }
252 
253   const std::string* hex_lac =
254       entry.FindStringKey(shill::kGeoLocationAreaCodeProperty);
255   if (hex_lac) {
256     ct.lac = HexToDecimal(*hex_lac);
257   }
258 
259   // Read decimal fields into object.
260   ct.mcc = FindStringOrEmpty(entry, shill::kGeoMobileCountryCodeProperty);
261   ct.mnc = FindStringOrEmpty(entry, shill::kGeoMobileNetworkCodeProperty);
262 
263   // Add new object to vector.
264   cell_towers_.push_back(ct);
265 }
266 
267 }  // namespace chromeos
268