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 "chromeos/network/sms_watcher.h" 6 7 #include <algorithm> 8 #include <deque> 9 #include <string> 10 #include <vector> 11 12 #include "base/bind.h" 13 #include "base/values.h" 14 #include "chromeos/dbus/dbus_thread_manager.h" 15 #include "chromeos/dbus/gsm_sms_client.h" 16 #include "chromeos/dbus/modem_messaging_client.h" 17 #include "chromeos/dbus/shill_device_client.h" 18 #include "chromeos/dbus/sms_client.h" 19 #include "third_party/cros_system_api/dbus/service_constants.h" 20 21 namespace chromeos { 22 23 namespace { 24 25 int decode_bcd(const char *s) { 26 return (s[0] - '0') * 10 + s[1] - '0'; 27 } 28 29 void decode_timestamp(const std::string& sms_timestamp, SMS *sms) { 30 base::Time::Exploded exp; 31 exp.year = decode_bcd(&sms_timestamp[0]); 32 if (exp.year > 95) 33 exp.year += 1900; 34 else 35 exp.year += 2000; 36 exp.month = decode_bcd(&sms_timestamp[2]); 37 exp.day_of_month = decode_bcd(&sms_timestamp[4]); 38 exp.hour = decode_bcd(&sms_timestamp[6]); 39 exp.minute = decode_bcd(&sms_timestamp[8]); 40 exp.second = decode_bcd(&sms_timestamp[10]); 41 exp.millisecond = 0; 42 sms->timestamp = base::Time::FromUTCExploded(exp); 43 int hours = decode_bcd(&sms_timestamp[13]); 44 if (sms_timestamp[12] == '-') 45 hours = -hours; 46 sms->timestamp -= base::TimeDelta::FromHours(hours); 47 } 48 49 // Callback for Delete() method. This method does nothing. 50 void DeleteSMSCallback() {} 51 52 } // namespace 53 54 const char SMSWatcher::kNumberKey[] = "number"; 55 const char SMSWatcher::kTextKey[] = "text"; 56 const char SMSWatcher::kTimestampKey[] = "timestamp"; 57 const char SMSWatcher::kSmscKey[] = "smsc"; 58 const char SMSWatcher::kValidityKey[] = "validity"; 59 const char SMSWatcher::kClassKey[] = "class"; 60 const char SMSWatcher::kIndexKey[] = "index"; 61 62 const char SMSWatcher::kModemManager1NumberKey[] = "Number"; 63 const char SMSWatcher::kModemManager1TextKey[] = "Text"; 64 const char SMSWatcher::kModemManager1TimestampKey[] = "Timestamp"; 65 const char SMSWatcher::kModemManager1SmscKey[] = "Smsc"; 66 const char SMSWatcher::kModemManager1ValidityKey[] = "Validity"; 67 const char SMSWatcher::kModemManager1ClassKey[] = "Class"; 68 const char SMSWatcher::kModemManager1IndexKey[] = "Index"; 69 70 class SMSWatcher::WatcherBase { 71 public: 72 WatcherBase(const std::string& device_path, 73 MonitorSMSCallback callback, 74 const std::string& dbus_connection, 75 const dbus::ObjectPath& object_path) : 76 device_path_(device_path), 77 callback_(callback), 78 dbus_connection_(dbus_connection), 79 object_path_(object_path) {} 80 81 virtual ~WatcherBase() {} 82 83 protected: 84 const std::string device_path_; 85 MonitorSMSCallback callback_; 86 const std::string dbus_connection_; 87 const dbus::ObjectPath object_path_; 88 89 DISALLOW_COPY_AND_ASSIGN(WatcherBase); 90 }; 91 92 namespace { 93 94 class GsmWatcher : public SMSWatcher::WatcherBase { 95 public: 96 GsmWatcher(const std::string& device_path, 97 MonitorSMSCallback callback, 98 const std::string& dbus_connection, 99 const dbus::ObjectPath& object_path) 100 : WatcherBase(device_path, callback, dbus_connection, object_path), 101 weak_ptr_factory_(this) { 102 DBusThreadManager::Get()->GetGsmSMSClient()->SetSmsReceivedHandler( 103 dbus_connection_, 104 object_path_, 105 base::Bind(&GsmWatcher::OnSmsReceived, weak_ptr_factory_.GetWeakPtr())); 106 107 DBusThreadManager::Get()->GetGsmSMSClient()->List( 108 dbus_connection_, object_path_, 109 base::Bind(&GsmWatcher::ListSMSCallback, 110 weak_ptr_factory_.GetWeakPtr())); 111 } 112 113 virtual ~GsmWatcher() { 114 DBusThreadManager::Get()->GetGsmSMSClient()->ResetSmsReceivedHandler( 115 dbus_connection_, object_path_); 116 } 117 118 private: 119 // Callback for SmsReceived signal from ModemManager.Modem.Gsm.SMS 120 void OnSmsReceived(uint32 index, bool complete) { 121 // Only handle complete messages. 122 if (!complete) 123 return; 124 DBusThreadManager::Get()->GetGsmSMSClient()->Get( 125 dbus_connection_, object_path_, index, 126 base::Bind(&GsmWatcher::GetSMSCallback, 127 weak_ptr_factory_.GetWeakPtr(), 128 index)); 129 } 130 131 // Runs |callback_| with a SMS. 132 void RunCallbackWithSMS(const base::DictionaryValue& sms_dictionary) { 133 SMS sms; 134 135 if (!sms_dictionary.GetStringWithoutPathExpansion(SMSWatcher::kNumberKey, 136 &sms.number)) 137 LOG(WARNING) << "SMS did not contain a number"; 138 139 if (!sms_dictionary.GetStringWithoutPathExpansion(SMSWatcher::kTextKey, 140 &sms.text)) 141 LOG(WARNING) << "SMS did not contain message text"; 142 143 std::string sms_timestamp; 144 if (sms_dictionary.GetStringWithoutPathExpansion(SMSWatcher::kTimestampKey, 145 &sms_timestamp)) { 146 decode_timestamp(sms_timestamp, &sms); 147 } else { 148 LOG(WARNING) << "SMS did not contain a timestamp"; 149 sms.timestamp = base::Time(); 150 } 151 152 sms_dictionary.GetStringWithoutPathExpansion(SMSWatcher::kSmscKey, 153 &sms.smsc); 154 155 double validity = 0; 156 if (!sms_dictionary.GetDoubleWithoutPathExpansion(SMSWatcher::kValidityKey, 157 &validity)) { 158 validity = -1; 159 } 160 sms.validity = validity; 161 162 double msgclass = 0; 163 if (!sms_dictionary.GetDoubleWithoutPathExpansion(SMSWatcher::kClassKey, 164 &msgclass)) { 165 msgclass = -1; 166 } 167 sms.msgclass = msgclass; 168 169 callback_.Run(device_path_, sms); 170 } 171 172 // Callback for Get() method from ModemManager.Modem.Gsm.SMS 173 void GetSMSCallback(uint32 index, 174 const base::DictionaryValue& sms_dictionary) { 175 RunCallbackWithSMS(sms_dictionary); 176 177 DBusThreadManager::Get()->GetGsmSMSClient()->Delete( 178 dbus_connection_, object_path_, index, base::Bind(&DeleteSMSCallback)); 179 } 180 181 // Callback for List() method. 182 void ListSMSCallback(const base::ListValue& result) { 183 // List() is called only once; no one touches delete_queue_ before List(). 184 CHECK(delete_queue_.empty()); 185 for (size_t i = 0; i != result.GetSize(); ++i) { 186 const base::DictionaryValue* sms_dictionary = NULL; 187 if (!result.GetDictionary(i, &sms_dictionary)) { 188 LOG(ERROR) << "result[" << i << "] is not a dictionary."; 189 continue; 190 } 191 RunCallbackWithSMS(*sms_dictionary); 192 double index = 0; 193 if (sms_dictionary->GetDoubleWithoutPathExpansion(SMSWatcher::kIndexKey, 194 &index)) { 195 delete_queue_.push_back(index); 196 } 197 } 198 DeleteSMSInChain(); 199 } 200 201 // Deletes SMSs in the queue. 202 void DeleteSMSInChain() { 203 if (!delete_queue_.empty()) { 204 DBusThreadManager::Get()->GetGsmSMSClient()->Delete( 205 dbus_connection_, object_path_, delete_queue_.back(), 206 base::Bind(&GsmWatcher::DeleteSMSInChain, 207 weak_ptr_factory_.GetWeakPtr())); 208 delete_queue_.pop_back(); 209 } 210 } 211 212 base::WeakPtrFactory<GsmWatcher> weak_ptr_factory_; 213 std::vector<uint32> delete_queue_; 214 215 DISALLOW_COPY_AND_ASSIGN(GsmWatcher); 216 }; 217 218 class ModemManager1Watcher : public SMSWatcher::WatcherBase { 219 public: 220 ModemManager1Watcher(const std::string& device_path, 221 MonitorSMSCallback callback, 222 const std::string& dbus_connection, 223 const dbus::ObjectPath& object_path) 224 : WatcherBase(device_path, callback, dbus_connection, object_path), 225 weak_ptr_factory_(this), 226 deleting_messages_(false), 227 retrieving_messages_(false) { 228 DBusThreadManager::Get()->GetModemMessagingClient()->SetSmsReceivedHandler( 229 dbus_connection_, 230 object_path_, 231 base::Bind(&ModemManager1Watcher::OnSmsReceived, 232 weak_ptr_factory_.GetWeakPtr())); 233 234 DBusThreadManager::Get()->GetModemMessagingClient()->List( 235 dbus_connection_, object_path_, 236 base::Bind(&ModemManager1Watcher::ListSMSCallback, 237 weak_ptr_factory_.GetWeakPtr())); 238 } 239 240 virtual ~ModemManager1Watcher() { 241 DBusThreadManager::Get()->GetModemMessagingClient() 242 ->ResetSmsReceivedHandler(dbus_connection_, object_path_); 243 } 244 245 private: 246 void ListSMSCallback( 247 const std::vector<dbus::ObjectPath>& paths) { 248 // This receives all messages, so clear any pending gets and deletes. 249 retrieval_queue_.clear(); 250 delete_queue_.clear(); 251 252 retrieval_queue_.resize(paths.size()); 253 std::copy(paths.begin(), paths.end(), retrieval_queue_.begin()); 254 if (!retrieving_messages_) 255 GetMessages(); 256 } 257 258 // Messages must be deleted one at a time, since we can not 259 // guarantee the order the deletion will be executed in. Delete 260 // messages from the back of the list so that the indices are 261 // valid. 262 void DeleteMessages() { 263 if (delete_queue_.empty()) { 264 deleting_messages_ = false; 265 return; 266 } 267 deleting_messages_ = true; 268 dbus::ObjectPath sms_path = delete_queue_.back(); 269 delete_queue_.pop_back(); 270 DBusThreadManager::Get()->GetModemMessagingClient()->Delete( 271 dbus_connection_, object_path_, sms_path, 272 base::Bind(&ModemManager1Watcher::DeleteMessages, 273 weak_ptr_factory_.GetWeakPtr())); 274 } 275 276 // Messages must be fetched one at a time, so that we do not queue too 277 // many requests to a single threaded server. 278 void GetMessages() { 279 if (retrieval_queue_.empty()) { 280 retrieving_messages_ = false; 281 if (!deleting_messages_) 282 DeleteMessages(); 283 return; 284 } 285 retrieving_messages_ = true; 286 dbus::ObjectPath sms_path = retrieval_queue_.front(); 287 retrieval_queue_.pop_front(); 288 DBusThreadManager::Get()->GetSMSClient()->GetAll( 289 dbus_connection_, sms_path, 290 base::Bind(&ModemManager1Watcher::GetCallback, 291 weak_ptr_factory_.GetWeakPtr())); 292 delete_queue_.push_back(sms_path); 293 } 294 295 // Handles arrival of a new SMS message. 296 void OnSmsReceived(const dbus::ObjectPath& sms_path, bool complete) { 297 // Only handle complete messages. 298 if (!complete) 299 return; 300 retrieval_queue_.push_back(sms_path); 301 if (!retrieving_messages_) 302 GetMessages(); 303 } 304 305 // Runs |callback_| with a SMS. 306 void RunCallbackWithSMS(const base::DictionaryValue& sms_dictionary) { 307 SMS sms; 308 309 if (!sms_dictionary.GetStringWithoutPathExpansion( 310 SMSWatcher::kModemManager1NumberKey, &sms.number)) 311 LOG(WARNING) << "SMS did not contain a number"; 312 313 if (!sms_dictionary.GetStringWithoutPathExpansion( 314 SMSWatcher::kModemManager1TextKey, &sms.text)) 315 LOG(WARNING) << "SMS did not contain message text"; 316 317 std::string sms_timestamp; 318 if (sms_dictionary.GetStringWithoutPathExpansion( 319 SMSWatcher::kModemManager1TimestampKey, &sms_timestamp)) { 320 decode_timestamp(sms_timestamp, &sms); 321 } else { 322 LOG(WARNING) << "SMS did not contain a timestamp"; 323 sms.timestamp = base::Time(); 324 } 325 326 sms_dictionary.GetStringWithoutPathExpansion( 327 SMSWatcher::kModemManager1SmscKey, &sms.smsc); 328 329 double validity = 0; 330 if (!sms_dictionary.GetDoubleWithoutPathExpansion( 331 SMSWatcher::kModemManager1ValidityKey, &validity)) { 332 validity = -1; 333 } 334 sms.validity = validity; 335 336 double msgclass = 0; 337 if (!sms_dictionary.GetDoubleWithoutPathExpansion( 338 SMSWatcher::kModemManager1ClassKey, &msgclass)) { 339 msgclass = -1; 340 } 341 sms.msgclass = msgclass; 342 343 callback_.Run(device_path_, sms); 344 } 345 346 void GetCallback(const base::DictionaryValue& dictionary) { 347 RunCallbackWithSMS(dictionary); 348 GetMessages(); 349 } 350 351 base::WeakPtrFactory<ModemManager1Watcher> weak_ptr_factory_; 352 bool deleting_messages_; 353 bool retrieving_messages_; 354 std::vector<dbus::ObjectPath> delete_queue_; 355 std::deque<dbus::ObjectPath> retrieval_queue_; 356 357 DISALLOW_COPY_AND_ASSIGN(ModemManager1Watcher); 358 }; 359 360 } // namespace 361 362 SMSWatcher::SMSWatcher(const std::string& modem_device_path, 363 MonitorSMSCallback callback) 364 : weak_ptr_factory_(this), 365 device_path_(modem_device_path), 366 callback_(callback) { 367 DBusThreadManager::Get()->GetShillDeviceClient()->GetProperties( 368 dbus::ObjectPath(modem_device_path), 369 base::Bind(&SMSWatcher::DevicePropertiesCallback, 370 weak_ptr_factory_.GetWeakPtr())); 371 } 372 373 SMSWatcher::~SMSWatcher() { 374 } 375 376 void SMSWatcher::DevicePropertiesCallback( 377 DBusMethodCallStatus call_status, 378 const base::DictionaryValue& properties) { 379 if (call_status != DBUS_METHOD_CALL_SUCCESS) 380 return; 381 382 std::string dbus_connection; 383 if (!properties.GetStringWithoutPathExpansion( 384 flimflam::kDBusConnectionProperty, &dbus_connection)) { 385 LOG(WARNING) << "Modem device properties do not include DBus connection."; 386 return; 387 } 388 389 std::string object_path_string; 390 if (!properties.GetStringWithoutPathExpansion( 391 flimflam::kDBusObjectProperty, &object_path_string)) { 392 LOG(WARNING) << "Modem device properties do not include DBus object."; 393 return; 394 } 395 396 if (object_path_string.compare( 397 0, sizeof(modemmanager::kModemManager1ServicePath) - 1, 398 modemmanager::kModemManager1ServicePath) == 0) { 399 watcher_.reset( 400 new ModemManager1Watcher(device_path_, callback_, dbus_connection, 401 dbus::ObjectPath(object_path_string))); 402 } else { 403 watcher_.reset( 404 new GsmWatcher(device_path_, callback_, dbus_connection, 405 dbus::ObjectPath(object_path_string))); 406 } 407 } 408 409 } // namespace chromeos 410