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