gcm_profile_service.cc revision f2477e01787aa58f445919b809d89e252beef54f
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 "chrome/browser/services/gcm/gcm_profile_service.h" 6 7#include "base/base64.h" 8#include "base/logging.h" 9#include "base/prefs/pref_service.h" 10#include "base/strings/string_number_conversions.h" 11#include "chrome/browser/chrome_notification_types.h" 12#include "chrome/browser/extensions/extension_service.h" 13#include "chrome/browser/extensions/extension_system.h" 14#include "chrome/browser/services/gcm/gcm_event_router.h" 15#include "chrome/browser/signin/signin_manager.h" 16#include "chrome/browser/signin/signin_manager_factory.h" 17#include "chrome/common/chrome_version_info.h" 18#include "chrome/common/pref_names.h" 19#include "components/user_prefs/pref_registry_syncable.h" 20#include "components/webdata/encryptor/encryptor.h" 21#include "content/public/browser/browser_thread.h" 22#include "content/public/browser/notification_details.h" 23#include "content/public/browser/notification_source.h" 24#include "extensions/common/extension.h" 25 26using extensions::Extension; 27 28namespace gcm { 29 30class GCMProfileService::IOWorker 31 : public GCMClient::Delegate, 32 public base::RefCountedThreadSafe<GCMProfileService::IOWorker>{ 33 public: 34 explicit IOWorker(const base::WeakPtr<GCMProfileService>& service); 35 36 // Overridden from GCMClient::Delegate: 37 // Called from IO thread. 38 virtual void OnCheckInFinished(const GCMClient::CheckInInfo& checkin_info, 39 GCMClient::Result result) OVERRIDE; 40 virtual void OnRegisterFinished(const std::string& app_id, 41 const std::string& registration_id, 42 GCMClient::Result result) OVERRIDE; 43 virtual void OnUnregisterFinished(const std::string& app_id, 44 GCMClient::Result result) OVERRIDE; 45 virtual void OnSendFinished(const std::string& app_id, 46 const std::string& message_id, 47 GCMClient::Result result) OVERRIDE; 48 virtual void OnMessageReceived( 49 const std::string& app_id, 50 const GCMClient::IncomingMessage& message) OVERRIDE; 51 virtual void OnMessagesDeleted(const std::string& app_id) OVERRIDE; 52 virtual void OnMessageSendError(const std::string& app_id, 53 const std::string& message_id, 54 GCMClient::Result result) OVERRIDE; 55 virtual GCMClient::CheckInInfo GetCheckInInfo() const OVERRIDE; 56 virtual void OnLoadingCompleted() OVERRIDE; 57 virtual base::TaskRunner* GetFileTaskRunner() OVERRIDE; 58 59 void CheckIn(const std::string& username); 60 void SetCheckInInfo(GCMClient::CheckInInfo checkin_info); 61 void CheckOut(); 62 void Register(const std::string& username, 63 const std::string& app_id, 64 const std::vector<std::string>& sender_ids, 65 const std::string& cert); 66 void Send(const std::string& username, 67 const std::string& app_id, 68 const std::string& receiver_id, 69 const GCMClient::OutgoingMessage& message); 70 71 private: 72 friend class base::RefCountedThreadSafe<IOWorker>; 73 virtual ~IOWorker(); 74 75 const base::WeakPtr<GCMProfileService> service_; 76 77 // The checkin info obtained from the server for the signed in user associated 78 // with the profile. 79 GCMClient::CheckInInfo checkin_info_; 80}; 81 82GCMProfileService::IOWorker::IOWorker( 83 const base::WeakPtr<GCMProfileService>& service) 84 : service_(service) { 85} 86 87GCMProfileService::IOWorker::~IOWorker() { 88} 89 90void GCMProfileService::IOWorker::OnCheckInFinished( 91 const GCMClient::CheckInInfo& checkin_info, 92 GCMClient::Result result) { 93 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 94 95 checkin_info_ = checkin_info; 96 97 content::BrowserThread::PostTask( 98 content::BrowserThread::UI, 99 FROM_HERE, 100 base::Bind(&GCMProfileService::CheckInFinished, 101 service_, 102 checkin_info_, 103 result)); 104} 105 106void GCMProfileService::IOWorker::OnRegisterFinished( 107 const std::string& app_id, 108 const std::string& registration_id, 109 GCMClient::Result result) { 110 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 111 112 content::BrowserThread::PostTask( 113 content::BrowserThread::UI, 114 FROM_HERE, 115 base::Bind(&GCMProfileService::RegisterFinished, 116 service_, 117 app_id, 118 registration_id, 119 result)); 120} 121 122void GCMProfileService::IOWorker::OnUnregisterFinished( 123 const std::string& app_id, 124 GCMClient::Result result) { 125 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 126 127 // TODO(jianli): to be implemented. 128} 129 130void GCMProfileService::IOWorker::OnSendFinished( 131 const std::string& app_id, 132 const std::string& message_id, 133 GCMClient::Result result) { 134 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 135 136 content::BrowserThread::PostTask( 137 content::BrowserThread::UI, 138 FROM_HERE, 139 base::Bind(&GCMProfileService::SendFinished, 140 service_, 141 app_id, 142 message_id, 143 result)); 144} 145 146void GCMProfileService::IOWorker::OnMessageReceived( 147 const std::string& app_id, 148 const GCMClient::IncomingMessage& message) { 149 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 150 151 content::BrowserThread::PostTask( 152 content::BrowserThread::UI, 153 FROM_HERE, 154 base::Bind(&GCMProfileService::MessageReceived, 155 service_, 156 app_id, 157 message)); 158} 159 160void GCMProfileService::IOWorker::OnMessagesDeleted(const std::string& app_id) { 161 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 162 163 content::BrowserThread::PostTask( 164 content::BrowserThread::UI, 165 FROM_HERE, 166 base::Bind(&GCMProfileService::MessagesDeleted, 167 service_, 168 app_id)); 169} 170 171void GCMProfileService::IOWorker::OnMessageSendError( 172 const std::string& app_id, 173 const std::string& message_id, 174 GCMClient::Result result) { 175 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 176 177 content::BrowserThread::PostTask( 178 content::BrowserThread::UI, 179 FROM_HERE, 180 base::Bind(&GCMProfileService::MessageSendError, 181 service_, 182 app_id, 183 message_id, 184 result)); 185} 186 187GCMClient::CheckInInfo GCMProfileService::IOWorker::GetCheckInInfo() const { 188 return checkin_info_; 189} 190 191void GCMProfileService::IOWorker::OnLoadingCompleted() { 192 // TODO(jianli): to be implemented. 193} 194 195base::TaskRunner* GCMProfileService::IOWorker::GetFileTaskRunner() { 196 // TODO(jianli): to be implemented. 197 return NULL; 198} 199 200void GCMProfileService::IOWorker::CheckIn(const std::string& username) { 201 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 202 203 GCMClient::Get()->CheckIn(username, this); 204} 205 206void GCMProfileService::IOWorker::SetCheckInInfo( 207 GCMClient::CheckInInfo checkin_info) { 208 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 209 210 checkin_info_ = checkin_info; 211} 212 213void GCMProfileService::IOWorker::CheckOut() { 214 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 215 216 checkin_info_.Reset(); 217} 218 219void GCMProfileService::IOWorker::Register( 220 const std::string& username, 221 const std::string& app_id, 222 const std::vector<std::string>& sender_ids, 223 const std::string& cert) { 224 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 225 DCHECK(checkin_info_.IsValid()); 226 227 GCMClient::Get()->Register(username, app_id, cert, sender_ids); 228} 229 230void GCMProfileService::IOWorker::Send( 231 const std::string& username, 232 const std::string& app_id, 233 const std::string& receiver_id, 234 const GCMClient::OutgoingMessage& message) { 235 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 236 DCHECK(checkin_info_.IsValid()); 237 238 GCMClient::Get()->Send(username, app_id, receiver_id, message); 239} 240 241bool GCMProfileService::enable_gcm_for_testing_ = false; 242 243// static 244bool GCMProfileService::IsGCMEnabled() { 245 if (enable_gcm_for_testing_) 246 return true; 247 248 // GCM support is only enabled for Canary/Dev builds. 249 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); 250 return channel == chrome::VersionInfo::CHANNEL_UNKNOWN || 251 channel == chrome::VersionInfo::CHANNEL_CANARY || 252 channel == chrome::VersionInfo::CHANNEL_DEV; 253} 254 255// static 256void GCMProfileService::RegisterProfilePrefs( 257 user_prefs::PrefRegistrySyncable* registry) { 258 registry->RegisterUint64Pref( 259 prefs::kGCMUserAccountID, 260 0, 261 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 262 registry->RegisterStringPref( 263 prefs::kGCMUserToken, 264 "", 265 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 266} 267 268GCMProfileService::GCMProfileService(Profile* profile) 269 : profile_(profile), 270 testing_delegate_(NULL), 271 weak_ptr_factory_(this) { 272 Init(); 273} 274 275GCMProfileService::GCMProfileService(Profile* profile, 276 TestingDelegate* testing_delegate) 277 : profile_(profile), 278 testing_delegate_(testing_delegate), 279 weak_ptr_factory_(this) { 280 Init(); 281} 282 283GCMProfileService::~GCMProfileService() { 284} 285 286void GCMProfileService::Init() { 287 // This has to be done first since CheckIn depends on it. 288 io_worker_ = new IOWorker(weak_ptr_factory_.GetWeakPtr()); 289 290 // In case that the profile has been signed in before GCMProfileService is 291 // created. 292 SigninManagerBase* manager = SigninManagerFactory::GetForProfile(profile_); 293 if (manager) 294 username_ = manager->GetAuthenticatedUsername(); 295 if (!username_.empty()) 296 AddUser(); 297 298 registrar_.Add(this, 299 chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL, 300 content::Source<Profile>(profile_)); 301 registrar_.Add(this, 302 chrome::NOTIFICATION_GOOGLE_SIGNED_OUT, 303 content::Source<Profile>(profile_)); 304} 305 306void GCMProfileService::Register(const std::string& app_id, 307 const std::vector<std::string>& sender_ids, 308 const std::string& cert, 309 RegisterCallback callback) { 310 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 311 DCHECK(!app_id.empty() && !sender_ids.empty() && !callback.is_null()); 312 313 if (register_callbacks_.find(app_id) != register_callbacks_.end()) { 314 callback.Run(std::string(), GCMClient::ASYNC_OPERATION_PENDING); 315 return; 316 } 317 register_callbacks_[app_id] = callback; 318 319 content::BrowserThread::PostTask( 320 content::BrowserThread::IO, 321 FROM_HERE, 322 base::Bind(&GCMProfileService::IOWorker::Register, 323 io_worker_, 324 username_, 325 app_id, 326 sender_ids, 327 cert)); 328} 329 330void GCMProfileService::Send(const std::string& app_id, 331 const std::string& receiver_id, 332 const GCMClient::OutgoingMessage& message, 333 SendCallback callback) { 334 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 335 DCHECK(!app_id.empty() && !receiver_id.empty() && !callback.is_null()); 336 337 std::pair<std::string, std::string> key(app_id, message.id); 338 if (send_callbacks_.find(key) != send_callbacks_.end()) { 339 callback.Run(message.id, GCMClient::INVALID_PARAMETER); 340 return; 341 } 342 send_callbacks_[key] = callback; 343 344 content::BrowserThread::PostTask( 345 content::BrowserThread::IO, 346 FROM_HERE, 347 base::Bind(&GCMProfileService::IOWorker::Send, 348 io_worker_, 349 username_, 350 app_id, 351 receiver_id, 352 message)); 353} 354 355void GCMProfileService::Observe(int type, 356 const content::NotificationSource& source, 357 const content::NotificationDetails& details) { 358 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 359 360 switch (type) { 361 case chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL: { 362 const GoogleServiceSigninSuccessDetails* signin_details = 363 content::Details<GoogleServiceSigninSuccessDetails>(details).ptr(); 364 // If re-signin occurs due to password change, there is no need to do 365 // check-in again. 366 if (username_ != signin_details->username) { 367 username_ = signin_details->username; 368 DCHECK(!username_.empty()); 369 AddUser(); 370 } 371 break; 372 } 373 case chrome::NOTIFICATION_GOOGLE_SIGNED_OUT: 374 username_.clear(); 375 RemoveUser(); 376 break; 377 default: 378 NOTREACHED(); 379 } 380} 381 382void GCMProfileService::AddUser() { 383 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 384 385 // Try to read persisted check-in info from the profile's prefs store. 386 PrefService* pref_service = profile_->GetPrefs(); 387 uint64 android_id = pref_service->GetUint64(prefs::kGCMUserAccountID); 388 std::string base64_token = pref_service->GetString(prefs::kGCMUserToken); 389 std::string encrypted_secret; 390 base::Base64Decode(base::StringPiece(base64_token), &encrypted_secret); 391 if (android_id && !encrypted_secret.empty()) { 392 std::string decrypted_secret; 393 Encryptor::DecryptString(encrypted_secret, &decrypted_secret); 394 uint64 secret = 0; 395 if (base::StringToUint64(decrypted_secret, &secret) && secret) { 396 GCMClient::CheckInInfo checkin_info; 397 checkin_info.android_id = android_id; 398 checkin_info.secret = secret; 399 content::BrowserThread::PostTask( 400 content::BrowserThread::IO, 401 FROM_HERE, 402 base::Bind(&GCMProfileService::IOWorker::SetCheckInInfo, 403 io_worker_, 404 checkin_info)); 405 406 if (testing_delegate_) 407 testing_delegate_->CheckInFinished(checkin_info, GCMClient::SUCCESS); 408 409 return; 410 } 411 } 412 413 content::BrowserThread::PostTask( 414 content::BrowserThread::IO, 415 FROM_HERE, 416 base::Bind(&GCMProfileService::IOWorker::CheckIn, 417 io_worker_, 418 username_)); 419} 420 421void GCMProfileService::RemoveUser() { 422 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 423 424 PrefService* pref_service = profile_->GetPrefs(); 425 pref_service->ClearPref(prefs::kGCMUserAccountID); 426 pref_service->ClearPref(prefs::kGCMUserToken); 427 428 content::BrowserThread::PostTask( 429 content::BrowserThread::IO, 430 FROM_HERE, 431 base::Bind(&GCMProfileService::IOWorker::CheckOut, io_worker_)); 432} 433 434void GCMProfileService::CheckInFinished(GCMClient::CheckInInfo checkin_info, 435 GCMClient::Result result) { 436 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 437 438 // Save the check-in info into the profile's prefs store. 439 PrefService* pref_service = profile_->GetPrefs(); 440 pref_service->SetUint64(prefs::kGCMUserAccountID, checkin_info.android_id); 441 442 // Encrypt the secret for persisting purpose. 443 std::string encrypted_secret; 444 Encryptor::EncryptString(base::Uint64ToString(checkin_info.secret), 445 &encrypted_secret); 446 447 // |encrypted_secret| might contain binary data and our prefs store only 448 // works for the text. 449 std::string base64_token; 450 base::Base64Encode(encrypted_secret, &base64_token); 451 pref_service->SetString(prefs::kGCMUserToken, base64_token); 452 453 if (testing_delegate_) 454 testing_delegate_->CheckInFinished(checkin_info, result); 455} 456 457void GCMProfileService::RegisterFinished(std::string app_id, 458 std::string registration_id, 459 GCMClient::Result result) { 460 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 461 462 std::map<std::string, RegisterCallback>::iterator callback_iter = 463 register_callbacks_.find(app_id); 464 if (callback_iter == register_callbacks_.end()) { 465 // The callback could have been removed when the app is uninstalled. 466 return; 467 } 468 469 RegisterCallback callback = callback_iter->second; 470 register_callbacks_.erase(callback_iter); 471 callback.Run(registration_id, result); 472} 473 474void GCMProfileService::SendFinished(std::string app_id, 475 std::string message_id, 476 GCMClient::Result result) { 477 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 478 479 std::map<std::pair<std::string, std::string>, SendCallback>::iterator 480 callback_iter = send_callbacks_.find( 481 std::pair<std::string, std::string>(app_id, message_id)); 482 if (callback_iter == send_callbacks_.end()) { 483 // The callback could have been removed when the app is uninstalled. 484 return; 485 } 486 487 SendCallback callback = callback_iter->second; 488 send_callbacks_.erase(callback_iter); 489 callback.Run(message_id, result); 490} 491 492void GCMProfileService::MessageReceived(std::string app_id, 493 GCMClient::IncomingMessage message) { 494 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 495 496 GetEventRouter(app_id)->OnMessage(app_id, message); 497} 498 499void GCMProfileService::MessagesDeleted(std::string app_id) { 500 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 501 502 GetEventRouter(app_id)->OnMessagesDeleted(app_id); 503} 504 505void GCMProfileService::MessageSendError(std::string app_id, 506 std::string message_id, 507 GCMClient::Result result) { 508 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 509 510 GetEventRouter(app_id)->OnSendError(app_id, message_id, result); 511} 512 513GCMEventRouter* GCMProfileService::GetEventRouter(const std::string& app_id) { 514 if (testing_delegate_ && testing_delegate_->GetEventRouter()) 515 return testing_delegate_->GetEventRouter(); 516 // TODO(fgorski): check and create the event router for JS routing. 517 return js_event_router_.get(); 518} 519 520} // namespace gcm 521