gcm_profile_service.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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 <algorithm>
8#include <string>
9#include <vector>
10
11#include "base/base64.h"
12#include "base/files/file_path.h"
13#include "base/logging.h"
14#include "base/path_service.h"
15#include "base/prefs/pref_service.h"
16#include "base/strings/string_number_conversions.h"
17#include "base/threading/sequenced_worker_pool.h"
18#include "chrome/browser/chrome_notification_types.h"
19#include "chrome/browser/profiles/profile.h"
20#include "chrome/browser/services/gcm/gcm_app_handler.h"
21#include "chrome/browser/services/gcm/gcm_client_factory.h"
22#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
23#include "chrome/browser/signin/signin_manager_factory.h"
24#include "chrome/common/chrome_constants.h"
25#include "chrome/common/chrome_paths.h"
26#include "chrome/common/chrome_version_info.h"
27#include "chrome/common/pref_names.h"
28#include "components/signin/core/browser/profile_oauth2_token_service.h"
29#include "components/signin/core/browser/signin_manager.h"
30#include "components/user_prefs/pref_registry_syncable.h"
31#include "content/public/browser/browser_thread.h"
32#include "content/public/browser/notification_details.h"
33#include "content/public/browser/notification_source.h"
34#include "google_apis/gcm/protocol/android_checkin.pb.h"
35#include "net/url_request/url_request_context_getter.h"
36
37namespace gcm {
38
39namespace {
40
41checkin_proto::ChromeBuildProto_Platform GetPlatform() {
42#if defined(OS_WIN)
43  return checkin_proto::ChromeBuildProto_Platform_PLATFORM_WIN;
44#elif defined(OS_MACOSX)
45  return checkin_proto::ChromeBuildProto_Platform_PLATFORM_MAC;
46#elif defined(OS_IOS)
47  return checkin_proto::ChromeBuildProto_Platform_PLATFORM_IOS;
48#elif defined(OS_CHROMEOS)
49  return checkin_proto::ChromeBuildProto_Platform_PLATFORM_CROS;
50#elif defined(OS_LINUX)
51  return checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX;
52#else
53  // For all other platforms, return as LINUX.
54  return checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX;
55#endif
56}
57
58std::string GetVersion() {
59  chrome::VersionInfo version_info;
60  return version_info.Version();
61}
62
63checkin_proto::ChromeBuildProto_Channel GetChannel() {
64  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
65  switch (channel) {
66    case chrome::VersionInfo::CHANNEL_UNKNOWN:
67      return checkin_proto::ChromeBuildProto_Channel_CHANNEL_UNKNOWN;
68    case chrome::VersionInfo::CHANNEL_CANARY:
69      return checkin_proto::ChromeBuildProto_Channel_CHANNEL_CANARY;
70    case chrome::VersionInfo::CHANNEL_DEV:
71      return checkin_proto::ChromeBuildProto_Channel_CHANNEL_DEV;
72    case chrome::VersionInfo::CHANNEL_BETA:
73      return checkin_proto::ChromeBuildProto_Channel_CHANNEL_BETA;
74    case chrome::VersionInfo::CHANNEL_STABLE:
75      return checkin_proto::ChromeBuildProto_Channel_CHANNEL_STABLE;
76    default:
77      NOTREACHED();
78      return checkin_proto::ChromeBuildProto_Channel_CHANNEL_UNKNOWN;
79  };
80}
81
82}  // namespace
83
84// Helper class to save tasks to run until we're ready to execute them.
85class GCMProfileService::DelayedTaskController {
86 public:
87  DelayedTaskController();
88  ~DelayedTaskController();
89
90  // Adds a task that will be invoked once we're ready.
91  void AddTask(base::Closure task);
92
93  // Sets ready status. It is ready only when check-in is completed and
94  // the GCMClient is fully initialized.
95  void SetReady();
96
97  // Returns true if it is ready to perform tasks.
98  bool CanRunTaskWithoutDelay() const;
99
100 private:
101  void RunTasks();
102
103  // Flag that indicates that GCM is ready.
104  bool ready_;
105
106  std::vector<base::Closure> delayed_tasks_;
107};
108
109GCMProfileService::DelayedTaskController::DelayedTaskController()
110    : ready_(false) {
111}
112
113GCMProfileService::DelayedTaskController::~DelayedTaskController() {
114}
115
116void GCMProfileService::DelayedTaskController::AddTask(base::Closure task) {
117  delayed_tasks_.push_back(task);
118}
119
120void GCMProfileService::DelayedTaskController::SetReady() {
121  ready_ = true;
122  RunTasks();
123}
124
125bool GCMProfileService::DelayedTaskController::CanRunTaskWithoutDelay() const {
126  return ready_;
127}
128
129void GCMProfileService::DelayedTaskController::RunTasks() {
130  DCHECK(ready_);
131
132  for (size_t i = 0; i < delayed_tasks_.size(); ++i)
133    delayed_tasks_[i].Run();
134  delayed_tasks_.clear();
135}
136
137class GCMProfileService::IOWorker
138    : public GCMClient::Delegate,
139      public base::RefCountedThreadSafe<GCMProfileService::IOWorker>{
140 public:
141  // Called on UI thread.
142  IOWorker();
143
144  // Overridden from GCMClient::Delegate:
145  // Called on IO thread.
146  virtual void OnRegisterFinished(const std::string& app_id,
147                                  const std::string& registration_id,
148                                  GCMClient::Result result) OVERRIDE;
149  virtual void OnUnregisterFinished(const std::string& app_id,
150                                    GCMClient::Result result) OVERRIDE;
151  virtual void OnSendFinished(const std::string& app_id,
152                              const std::string& message_id,
153                              GCMClient::Result result) OVERRIDE;
154  virtual void OnMessageReceived(
155      const std::string& app_id,
156      const GCMClient::IncomingMessage& message) OVERRIDE;
157  virtual void OnMessagesDeleted(const std::string& app_id) OVERRIDE;
158  virtual void OnMessageSendError(
159      const std::string& app_id,
160      const GCMClient::SendErrorDetails& send_error_details) OVERRIDE;
161  virtual void OnGCMReady() OVERRIDE;
162
163  // Called on IO thread.
164  void Initialize(scoped_ptr<GCMClientFactory> gcm_client_factory,
165                  const base::FilePath& store_path,
166                  const std::vector<std::string>& account_ids,
167                  const scoped_refptr<net::URLRequestContextGetter>&
168                      url_request_context_getter);
169  void Reset();
170  void Load(const base::WeakPtr<GCMProfileService>& service);
171  void Stop();
172  void CheckOut();
173  void Register(const std::string& app_id,
174                const std::vector<std::string>& sender_ids);
175  void Unregister(const std::string& app_id);
176  void Send(const std::string& app_id,
177            const std::string& receiver_id,
178            const GCMClient::OutgoingMessage& message);
179  void GetGCMStatistics(bool clear_logs);
180  void SetGCMRecording(bool recording);
181
182  // For testing purpose. Can be called from UI thread. Use with care.
183  GCMClient* gcm_client_for_testing() const { return gcm_client_.get(); }
184
185 private:
186  friend class base::RefCountedThreadSafe<IOWorker>;
187  virtual ~IOWorker();
188
189  base::WeakPtr<GCMProfileService> service_;
190
191  scoped_ptr<GCMClient> gcm_client_;
192};
193
194GCMProfileService::IOWorker::IOWorker() {
195  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
196}
197
198GCMProfileService::IOWorker::~IOWorker() {
199}
200
201void GCMProfileService::IOWorker::Initialize(
202    scoped_ptr<GCMClientFactory> gcm_client_factory,
203    const base::FilePath& store_path,
204    const std::vector<std::string>& account_ids,
205    const scoped_refptr<net::URLRequestContextGetter>&
206        url_request_context_getter) {
207  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
208
209  gcm_client_ = gcm_client_factory->BuildInstance().Pass();
210
211  checkin_proto::ChromeBuildProto chrome_build_proto;
212  chrome_build_proto.set_platform(GetPlatform());
213  chrome_build_proto.set_chrome_version(GetVersion());
214  chrome_build_proto.set_channel(GetChannel());
215
216  scoped_refptr<base::SequencedWorkerPool> worker_pool(
217      content::BrowserThread::GetBlockingPool());
218  scoped_refptr<base::SequencedTaskRunner> blocking_task_runner(
219      worker_pool->GetSequencedTaskRunnerWithShutdownBehavior(
220          worker_pool->GetSequenceToken(),
221          base::SequencedWorkerPool::SKIP_ON_SHUTDOWN));
222
223  gcm_client_->Initialize(chrome_build_proto,
224                          store_path,
225                          account_ids,
226                          blocking_task_runner,
227                          url_request_context_getter,
228                          this);
229}
230
231void GCMProfileService::IOWorker::Reset() {
232  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
233
234  // GCMClient instance must be destroyed from the same thread where it was
235  // created.
236  gcm_client_.reset();
237}
238
239void GCMProfileService::IOWorker::OnRegisterFinished(
240    const std::string& app_id,
241    const std::string& registration_id,
242    GCMClient::Result result) {
243  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
244
245  content::BrowserThread::PostTask(
246      content::BrowserThread::UI,
247      FROM_HERE,
248      base::Bind(&GCMProfileService::RegisterFinished,
249                 service_,
250                 app_id,
251                 registration_id,
252                 result));
253}
254
255void GCMProfileService::IOWorker::OnUnregisterFinished(
256    const std::string& app_id,
257    GCMClient::Result result) {
258  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
259
260  content::BrowserThread::PostTask(
261      content::BrowserThread::UI,
262      FROM_HERE,
263      base::Bind(
264          &GCMProfileService::UnregisterFinished, service_, app_id, result));
265}
266
267void GCMProfileService::IOWorker::OnSendFinished(
268    const std::string& app_id,
269    const std::string& message_id,
270    GCMClient::Result result) {
271  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
272
273  content::BrowserThread::PostTask(
274      content::BrowserThread::UI,
275      FROM_HERE,
276      base::Bind(&GCMProfileService::SendFinished,
277                 service_,
278                 app_id,
279                 message_id,
280                 result));
281}
282
283void GCMProfileService::IOWorker::OnMessageReceived(
284    const std::string& app_id,
285    const GCMClient::IncomingMessage& message) {
286  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
287
288  content::BrowserThread::PostTask(
289      content::BrowserThread::UI,
290      FROM_HERE,
291      base::Bind(&GCMProfileService::MessageReceived,
292                 service_,
293                 app_id,
294                 message));
295}
296
297void GCMProfileService::IOWorker::OnMessagesDeleted(const std::string& app_id) {
298  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
299
300  content::BrowserThread::PostTask(
301      content::BrowserThread::UI,
302      FROM_HERE,
303      base::Bind(&GCMProfileService::MessagesDeleted,
304                 service_,
305                 app_id));
306}
307
308void GCMProfileService::IOWorker::OnMessageSendError(
309    const std::string& app_id,
310    const GCMClient::SendErrorDetails& send_error_details) {
311  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
312
313  content::BrowserThread::PostTask(
314      content::BrowserThread::UI,
315      FROM_HERE,
316      base::Bind(&GCMProfileService::MessageSendError,
317                 service_,
318                 app_id,
319                 send_error_details));
320}
321
322void GCMProfileService::IOWorker::OnGCMReady() {
323  content::BrowserThread::PostTask(
324      content::BrowserThread::UI,
325      FROM_HERE,
326      base::Bind(&GCMProfileService::GCMClientReady,
327                 service_));
328}
329
330void GCMProfileService::IOWorker::Load(
331    const base::WeakPtr<GCMProfileService>& service) {
332  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
333
334  service_ = service;
335  gcm_client_->Load();
336}
337
338void GCMProfileService::IOWorker::Stop() {
339  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
340
341  gcm_client_->Stop();
342}
343
344void GCMProfileService::IOWorker::CheckOut() {
345  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
346
347  gcm_client_->CheckOut();
348
349  // Note that we still need to keep GCMClient instance alive since the profile
350  // might be signed in again.
351}
352
353void GCMProfileService::IOWorker::Register(
354    const std::string& app_id,
355    const std::vector<std::string>& sender_ids) {
356  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
357
358  gcm_client_->Register(app_id, sender_ids);
359}
360
361void GCMProfileService::IOWorker::Unregister(const std::string& app_id) {
362  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
363
364  gcm_client_->Unregister(app_id);
365}
366
367void GCMProfileService::IOWorker::Send(
368    const std::string& app_id,
369    const std::string& receiver_id,
370    const GCMClient::OutgoingMessage& message) {
371  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
372
373  gcm_client_->Send(app_id, receiver_id, message);
374}
375
376void GCMProfileService::IOWorker::GetGCMStatistics(bool clear_logs) {
377  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
378  gcm::GCMClient::GCMStatistics stats;
379
380  if (gcm_client_.get()) {
381    if (clear_logs)
382      gcm_client_->ClearActivityLogs();
383    stats = gcm_client_->GetStatistics();
384  }
385
386  content::BrowserThread::PostTask(
387      content::BrowserThread::UI,
388      FROM_HERE,
389      base::Bind(&GCMProfileService::GetGCMStatisticsFinished,
390                 service_,
391                 stats));
392}
393
394void GCMProfileService::IOWorker::SetGCMRecording(bool recording) {
395  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
396  gcm::GCMClient::GCMStatistics stats;
397
398  if (gcm_client_.get()) {
399    gcm_client_->SetRecording(recording);
400    stats = gcm_client_->GetStatistics();
401    stats.gcm_client_created = true;
402  }
403
404  content::BrowserThread::PostTask(
405      content::BrowserThread::UI,
406      FROM_HERE,
407      base::Bind(&GCMProfileService::GetGCMStatisticsFinished,
408                 service_,
409                 stats));
410}
411
412std::string GCMProfileService::GetGCMEnabledStateString(GCMEnabledState state) {
413  switch (state) {
414    case GCMProfileService::ALWAYS_ENABLED:
415      return "ALWAYS_ENABLED";
416    case GCMProfileService::ENABLED_FOR_APPS:
417      return "ENABLED_FOR_APPS";
418    case GCMProfileService::ALWAYS_DISABLED:
419      return "ALWAYS_DISABLED";
420    default:
421      NOTREACHED();
422      return std::string();
423  }
424}
425
426// static
427GCMProfileService::GCMEnabledState GCMProfileService::GetGCMEnabledState(
428    Profile* profile) {
429  const base::Value* gcm_enabled_value =
430      profile->GetPrefs()->GetUserPrefValue(prefs::kGCMChannelEnabled);
431  if (!gcm_enabled_value)
432    return ENABLED_FOR_APPS;
433
434  bool gcm_enabled = false;
435  if (!gcm_enabled_value->GetAsBoolean(&gcm_enabled))
436    return ENABLED_FOR_APPS;
437
438  return gcm_enabled ? ALWAYS_ENABLED : ALWAYS_DISABLED;
439}
440
441// static
442void GCMProfileService::RegisterProfilePrefs(
443    user_prefs::PrefRegistrySyncable* registry) {
444  // GCM support is only enabled by default for Canary/Dev/Custom builds.
445  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
446  bool on_by_default = false;
447  if (channel == chrome::VersionInfo::CHANNEL_UNKNOWN ||
448      channel == chrome::VersionInfo::CHANNEL_CANARY ||
449      channel == chrome::VersionInfo::CHANNEL_DEV) {
450    on_by_default = true;
451  }
452  registry->RegisterBooleanPref(
453      prefs::kGCMChannelEnabled,
454      on_by_default,
455      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
456}
457
458GCMProfileService::GCMProfileService(Profile* profile)
459    : profile_(profile),
460      gcm_client_ready_(false),
461      weak_ptr_factory_(this) {
462  DCHECK(!profile->IsOffTheRecord());
463}
464
465GCMProfileService::~GCMProfileService() {
466}
467
468void GCMProfileService::Initialize(
469    scoped_ptr<GCMClientFactory> gcm_client_factory) {
470  registrar_.Add(this,
471                 chrome::NOTIFICATION_PROFILE_DESTROYED,
472                 content::Source<Profile>(profile_));
473
474  SigninManagerFactory::GetForProfile(profile_)->AddObserver(this);
475
476  // Get the list of available accounts.
477  std::vector<std::string> account_ids;
478#if !defined(OS_ANDROID)
479  account_ids =
480      ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)->GetAccounts();
481#endif
482
483  // Create and initialize the GCMClient. Note that this does not initiate the
484  // GCM check-in.
485  io_worker_ = new IOWorker();
486  scoped_refptr<net::URLRequestContextGetter> url_request_context_getter =
487      profile_->GetRequestContext();
488  content::BrowserThread::PostTask(
489      content::BrowserThread::IO,
490      FROM_HERE,
491      base::Bind(&GCMProfileService::IOWorker::Initialize,
492                 io_worker_,
493                 base::Passed(&gcm_client_factory),
494                 profile_->GetPath().Append(chrome::kGCMStoreDirname),
495                 account_ids,
496                 url_request_context_getter));
497
498  // Load from the GCM store and initiate the GCM check-in if the rollout signal
499  // indicates yes.
500  if (GetGCMEnabledState(profile_) == ALWAYS_ENABLED)
501    EnsureLoaded();
502}
503
504void GCMProfileService::Start() {
505  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
506
507  EnsureLoaded();
508}
509
510void GCMProfileService::Stop() {
511  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
512
513  // No need to stop GCM service if not started yet.
514  if (username_.empty())
515    return;
516
517  RemoveCachedData();
518
519  content::BrowserThread::PostTask(
520      content::BrowserThread::IO,
521      FROM_HERE,
522      base::Bind(&GCMProfileService::IOWorker::Stop, io_worker_));
523}
524
525void GCMProfileService::Shutdown() {
526  for (GCMAppHandlerMap::const_iterator iter = app_handlers_.begin();
527       iter != app_handlers_.end(); ++iter) {
528    iter->second->ShutdownHandler();
529  }
530  app_handlers_.clear();
531
532  SigninManagerFactory::GetForProfile(profile_)->RemoveObserver(this);
533}
534
535void GCMProfileService::AddAppHandler(const std::string& app_id,
536                                      GCMAppHandler* handler) {
537  DCHECK(!app_id.empty());
538  DCHECK(handler);
539  DCHECK(app_handlers_.find(app_id) == app_handlers_.end());
540
541  app_handlers_[app_id] = handler;
542}
543
544void GCMProfileService::RemoveAppHandler(const std::string& app_id) {
545  DCHECK(!app_id.empty());
546
547  app_handlers_.erase(app_id);
548}
549
550void GCMProfileService::Register(const std::string& app_id,
551                                 const std::vector<std::string>& sender_ids,
552                                 RegisterCallback callback) {
553  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
554  DCHECK(!app_id.empty() && !sender_ids.empty() && !callback.is_null());
555
556  GCMClient::Result result = EnsureAppReady(app_id);
557  if (result != GCMClient::SUCCESS) {
558    callback.Run(std::string(), result);
559    return;
560  }
561
562  // If previous un/register operation is still in progress, bail out.
563  if (IsAsyncOperationPending(app_id)) {
564    callback.Run(std::string(), GCMClient::ASYNC_OPERATION_PENDING);
565    return;
566  }
567
568  register_callbacks_[app_id] = callback;
569
570  // Delay the register operation until GCMClient is ready.
571  if (!delayed_task_controller_->CanRunTaskWithoutDelay()) {
572    delayed_task_controller_->AddTask(
573        base::Bind(&GCMProfileService::DoRegister,
574                   weak_ptr_factory_.GetWeakPtr(),
575                   app_id,
576                   sender_ids));
577    return;
578  }
579
580  DoRegister(app_id, sender_ids);
581}
582
583void GCMProfileService::DoRegister(const std::string& app_id,
584                                   const std::vector<std::string>& sender_ids) {
585  std::map<std::string, RegisterCallback>::iterator callback_iter =
586      register_callbacks_.find(app_id);
587  if (callback_iter == register_callbacks_.end()) {
588    // The callback could have been removed when the app is uninstalled.
589    return;
590  }
591
592  // Normalize the sender IDs by making them sorted.
593  std::vector<std::string> normalized_sender_ids = sender_ids;
594  std::sort(normalized_sender_ids.begin(), normalized_sender_ids.end());
595
596  content::BrowserThread::PostTask(
597      content::BrowserThread::IO,
598      FROM_HERE,
599      base::Bind(&GCMProfileService::IOWorker::Register,
600                 io_worker_,
601                 app_id,
602                 normalized_sender_ids));
603}
604
605void GCMProfileService::Unregister(const std::string& app_id,
606                                   UnregisterCallback callback) {
607  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
608  DCHECK(!app_id.empty() && !callback.is_null());
609
610  GCMClient::Result result = EnsureAppReady(app_id);
611  if (result != GCMClient::SUCCESS) {
612    callback.Run(result);
613    return;
614  }
615
616  // If previous un/register operation is still in progress, bail out.
617  if (IsAsyncOperationPending(app_id)) {
618    callback.Run(GCMClient::ASYNC_OPERATION_PENDING);
619    return;
620  }
621
622  unregister_callbacks_[app_id] = callback;
623
624  // Delay the unregister operation until GCMClient is ready.
625  if (!delayed_task_controller_->CanRunTaskWithoutDelay()) {
626    delayed_task_controller_->AddTask(
627        base::Bind(&GCMProfileService::DoUnregister,
628                   weak_ptr_factory_.GetWeakPtr(),
629                   app_id));
630    return;
631  }
632
633  DoUnregister(app_id);
634}
635
636void GCMProfileService::DoUnregister(const std::string& app_id) {
637  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
638
639  // Ask the server to unregister it. There could be a small chance that the
640  // unregister request fails. If this occurs, it does not bring any harm since
641  // we simply reject the messages/events received from the server.
642  content::BrowserThread::PostTask(
643      content::BrowserThread::IO,
644      FROM_HERE,
645      base::Bind(&GCMProfileService::IOWorker::Unregister,
646                 io_worker_,
647                 app_id));
648}
649
650void GCMProfileService::Send(const std::string& app_id,
651                             const std::string& receiver_id,
652                             const GCMClient::OutgoingMessage& message,
653                             SendCallback callback) {
654  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
655  DCHECK(!app_id.empty() && !receiver_id.empty() && !callback.is_null());
656
657  GCMClient::Result result = EnsureAppReady(app_id);
658  if (result != GCMClient::SUCCESS) {
659    callback.Run(std::string(), result);
660    return;
661  }
662
663  // If the message with send ID is still in progress, bail out.
664  std::pair<std::string, std::string> key(app_id, message.id);
665  if (send_callbacks_.find(key) != send_callbacks_.end()) {
666    callback.Run(message.id, GCMClient::INVALID_PARAMETER);
667    return;
668  }
669
670  send_callbacks_[key] = callback;
671
672  // Delay the send operation until all GCMClient is ready.
673  if (!delayed_task_controller_->CanRunTaskWithoutDelay()) {
674    delayed_task_controller_->AddTask(
675        base::Bind(&GCMProfileService::DoSend,
676                   weak_ptr_factory_.GetWeakPtr(),
677                   app_id,
678                   receiver_id,
679                   message));
680    return;
681  }
682
683  DoSend(app_id, receiver_id, message);
684}
685
686void GCMProfileService::DoSend(const std::string& app_id,
687                               const std::string& receiver_id,
688                               const GCMClient::OutgoingMessage& message) {
689  content::BrowserThread::PostTask(
690      content::BrowserThread::IO,
691      FROM_HERE,
692      base::Bind(&GCMProfileService::IOWorker::Send,
693                 io_worker_,
694                 app_id,
695                 receiver_id,
696                 message));
697}
698
699GCMClient* GCMProfileService::GetGCMClientForTesting() const {
700  return io_worker_ ? io_worker_->gcm_client_for_testing() : NULL;
701}
702
703std::string GCMProfileService::SignedInUserName() const {
704  return username_;
705}
706
707bool GCMProfileService::IsGCMClientReady() const {
708  return gcm_client_ready_;
709}
710
711void GCMProfileService::GetGCMStatistics(
712    GetGCMStatisticsCallback callback, bool clear_logs) {
713  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
714  DCHECK(!callback.is_null());
715
716  request_gcm_statistics_callback_ = callback;
717  content::BrowserThread::PostTask(
718      content::BrowserThread::IO,
719      FROM_HERE,
720      base::Bind(&GCMProfileService::IOWorker::GetGCMStatistics,
721                 io_worker_,
722                 clear_logs));
723}
724
725void GCMProfileService::SetGCMRecording(
726    GetGCMStatisticsCallback callback, bool recording) {
727  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
728
729  request_gcm_statistics_callback_ = callback;
730  content::BrowserThread::PostTask(
731      content::BrowserThread::IO,
732      FROM_HERE,
733      base::Bind(&GCMProfileService::IOWorker::SetGCMRecording,
734                 io_worker_,
735                 recording));
736}
737
738void GCMProfileService::Observe(int type,
739                                const content::NotificationSource& source,
740                                const content::NotificationDetails& details) {
741  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
742
743  switch (type) {
744    case chrome::NOTIFICATION_PROFILE_DESTROYED:
745      ResetGCMClient();
746      break;
747    default:
748      NOTREACHED();
749  }
750}
751
752void GCMProfileService::GoogleSigninSucceeded(const std::string& username,
753                                              const std::string& password) {
754  if (GetGCMEnabledState(profile_) == ALWAYS_ENABLED)
755    EnsureLoaded();
756}
757
758void GCMProfileService::GoogleSignedOut(const std::string& username) {
759  CheckOut();
760}
761
762void GCMProfileService::EnsureLoaded() {
763  SigninManagerBase* manager = SigninManagerFactory::GetForProfile(profile_);
764  if (!manager)
765    return;
766  std::string username = manager->GetAuthenticatedUsername();
767  if (username.empty())
768    return;
769
770  // CheckIn could be called more than once when:
771  // 1) The password changes.
772  // 2) Register/send function calls it to ensure CheckIn is done.
773  if (username_ == username)
774    return;
775  username_ = username;
776
777  DCHECK(!delayed_task_controller_);
778  delayed_task_controller_.reset(new DelayedTaskController);
779
780  // This will load the data from the gcm store and trigger the check-in if
781  // the persisted check-in info is not found.
782  // Note that we need to pass weak pointer again since the existing weak
783  // pointer in IOWorker might have been invalidated when check-out occurs.
784  content::BrowserThread::PostTask(
785      content::BrowserThread::IO,
786      FROM_HERE,
787      base::Bind(&GCMProfileService::IOWorker::Load,
788                 io_worker_,
789                 weak_ptr_factory_.GetWeakPtr()));
790}
791
792void GCMProfileService::RemoveCachedData() {
793  // Remove all the queued tasks since they no longer make sense after
794  // GCM service is stopped.
795  weak_ptr_factory_.InvalidateWeakPtrs();
796
797  username_.clear();
798  gcm_client_ready_ = false;
799  delayed_task_controller_.reset();
800  register_callbacks_.clear();
801  send_callbacks_.clear();
802}
803
804void GCMProfileService::CheckOut() {
805  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
806
807  // We still proceed with the check-out logic even if the check-in is not
808  // initiated in the current session. This will make sure that all the
809  // persisted data written previously will get purged.
810
811  RemoveCachedData();
812
813  content::BrowserThread::PostTask(
814      content::BrowserThread::IO,
815      FROM_HERE,
816      base::Bind(&GCMProfileService::IOWorker::CheckOut, io_worker_));
817}
818
819void GCMProfileService::ResetGCMClient() {
820  content::BrowserThread::PostTask(
821      content::BrowserThread::IO,
822      FROM_HERE,
823      base::Bind(&GCMProfileService::IOWorker::Reset, io_worker_));
824}
825
826GCMClient::Result GCMProfileService::EnsureAppReady(const std::string& app_id) {
827  // Ensure that check-in has been done.
828  EnsureLoaded();
829
830  // If the profile was not signed in, bail out.
831  if (username_.empty())
832    return GCMClient::NOT_SIGNED_IN;
833
834  return GCMClient::SUCCESS;
835}
836
837bool GCMProfileService::IsAsyncOperationPending(
838    const std::string& app_id) const {
839  return register_callbacks_.find(app_id) != register_callbacks_.end() ||
840         unregister_callbacks_.find(app_id) != unregister_callbacks_.end();
841}
842
843void GCMProfileService::RegisterFinished(const std::string& app_id,
844                                         const std::string& registration_id,
845                                         GCMClient::Result result) {
846  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
847
848  std::map<std::string, RegisterCallback>::iterator callback_iter =
849      register_callbacks_.find(app_id);
850  if (callback_iter == register_callbacks_.end()) {
851    // The callback could have been removed when the app is uninstalled.
852    return;
853  }
854
855  RegisterCallback callback = callback_iter->second;
856  register_callbacks_.erase(callback_iter);
857  callback.Run(registration_id, result);
858}
859
860void GCMProfileService::UnregisterFinished(const std::string& app_id,
861                                           GCMClient::Result result) {
862  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
863
864  std::map<std::string, UnregisterCallback>::iterator callback_iter =
865      unregister_callbacks_.find(app_id);
866  if (callback_iter == unregister_callbacks_.end())
867    return;
868
869  UnregisterCallback callback = callback_iter->second;
870  unregister_callbacks_.erase(callback_iter);
871  callback.Run(result);
872}
873
874void GCMProfileService::SendFinished(const std::string& app_id,
875                                     const std::string& message_id,
876                                     GCMClient::Result result) {
877  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
878
879  std::map<std::pair<std::string, std::string>, SendCallback>::iterator
880      callback_iter = send_callbacks_.find(
881          std::pair<std::string, std::string>(app_id, message_id));
882  if (callback_iter == send_callbacks_.end()) {
883    // The callback could have been removed when the app is uninstalled.
884    return;
885  }
886
887  SendCallback callback = callback_iter->second;
888  send_callbacks_.erase(callback_iter);
889  callback.Run(message_id, result);
890}
891
892void GCMProfileService::MessageReceived(const std::string& app_id,
893                                        GCMClient::IncomingMessage message) {
894  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
895
896  // Drop the event if signed out.
897  if (username_.empty())
898    return;
899
900  GetAppHandler(app_id)->OnMessage(app_id, message);
901}
902
903void GCMProfileService::MessagesDeleted(const std::string& app_id) {
904  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
905
906  // Drop the event if signed out.
907  if (username_.empty())
908    return;
909
910  GetAppHandler(app_id)->OnMessagesDeleted(app_id);
911}
912
913void GCMProfileService::MessageSendError(
914    const std::string& app_id,
915    const GCMClient::SendErrorDetails& send_error_details) {
916  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
917
918  // Drop the event if signed out.
919  if (username_.empty())
920    return;
921
922  GetAppHandler(app_id)->OnSendError(app_id, send_error_details);
923}
924
925void GCMProfileService::GCMClientReady() {
926  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
927
928  if (gcm_client_ready_)
929    return;
930  gcm_client_ready_ = true;
931
932  delayed_task_controller_->SetReady();
933}
934
935GCMAppHandler* GCMProfileService::GetAppHandler(const std::string& app_id) {
936  std::map<std::string, GCMAppHandler*>::const_iterator iter =
937      app_handlers_.find(app_id);
938  return iter == app_handlers_.end() ? &default_app_handler_ : iter->second;
939}
940
941void GCMProfileService::GetGCMStatisticsFinished(
942    GCMClient::GCMStatistics stats) {
943  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
944  request_gcm_statistics_callback_.Run(stats);
945}
946
947}  // namespace gcm
948