summaryrefslogtreecommitdiffstats
path: root/components/gcm_driver
diff options
context:
space:
mode:
authorjianli@chromium.org <jianli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-28 03:18:49 +0000
committerjianli@chromium.org <jianli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-28 03:18:49 +0000
commit8d41e5a69e84b2d02e7289ef2f8877e20962400d (patch)
tree891f982a88639fc11125ec0dd3ff923834a74e14 /components/gcm_driver
parent07c770d1a21bce5af5eb0d79d6c02337ddedfe10 (diff)
downloadchromium_src-8d41e5a69e84b2d02e7289ef2f8877e20962400d.zip
chromium_src-8d41e5a69e84b2d02e7289ef2f8877e20962400d.tar.gz
chromium_src-8d41e5a69e84b2d02e7289ef2f8877e20962400d.tar.bz2
Move all remaining files to gcm_driver component
BUG=356716 TEST=existing tests TBR=pavely@chromium.org,kalman@chromium.org,arv@chromium.org Review URL: https://codereview.chromium.org/290013011 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@273140 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components/gcm_driver')
-rw-r--r--components/gcm_driver/DEPS2
-rw-r--r--components/gcm_driver/fake_gcm_app_handler.cc62
-rw-r--r--components/gcm_driver/fake_gcm_app_handler.h63
-rw-r--r--components/gcm_driver/fake_gcm_client.cc221
-rw-r--r--components/gcm_driver/fake_gcm_client.h102
-rw-r--r--components/gcm_driver/fake_gcm_client_factory.cc30
-rw-r--r--components/gcm_driver/fake_gcm_client_factory.h42
-rw-r--r--components/gcm_driver/gcm_driver.cc821
-rw-r--r--components/gcm_driver/gcm_driver.h233
-rw-r--r--components/gcm_driver/gcm_driver_unittest.cc900
10 files changed, 2476 insertions, 0 deletions
diff --git a/components/gcm_driver/DEPS b/components/gcm_driver/DEPS
index b8c50da..1004e94 100644
--- a/components/gcm_driver/DEPS
+++ b/components/gcm_driver/DEPS
@@ -1,5 +1,7 @@
include_rules = [
"+components/os_crypt",
+ "+google_apis/gaia",
"+google_apis/gcm",
"+jni",
+ "+net",
]
diff --git a/components/gcm_driver/fake_gcm_app_handler.cc b/components/gcm_driver/fake_gcm_app_handler.cc
new file mode 100644
index 0000000..632572b
--- /dev/null
+++ b/components/gcm_driver/fake_gcm_app_handler.cc
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/gcm_driver/fake_gcm_app_handler.h"
+
+#include "base/run_loop.h"
+
+namespace gcm {
+
+FakeGCMAppHandler::FakeGCMAppHandler() : received_event_(NO_EVENT) {
+}
+
+FakeGCMAppHandler::~FakeGCMAppHandler() {
+}
+
+void FakeGCMAppHandler::WaitForNotification() {
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ run_loop_.reset();
+}
+
+void FakeGCMAppHandler::ShutdownHandler() {
+}
+
+void FakeGCMAppHandler::OnMessage(const std::string& app_id,
+ const GCMClient::IncomingMessage& message) {
+ ClearResults();
+ received_event_ = MESSAGE_EVENT;
+ app_id_ = app_id;
+ message_ = message;
+ if (run_loop_)
+ run_loop_->Quit();
+}
+
+void FakeGCMAppHandler::OnMessagesDeleted(const std::string& app_id) {
+ ClearResults();
+ received_event_ = MESSAGES_DELETED_EVENT;
+ app_id_ = app_id;
+ if (run_loop_)
+ run_loop_->Quit();
+}
+
+void FakeGCMAppHandler::OnSendError(
+ const std::string& app_id,
+ const GCMClient::SendErrorDetails& send_error_details) {
+ ClearResults();
+ received_event_ = SEND_ERROR_EVENT;
+ app_id_ = app_id;
+ send_error_details_ = send_error_details;
+ if (run_loop_)
+ run_loop_->Quit();
+}
+
+void FakeGCMAppHandler::ClearResults() {
+ received_event_ = NO_EVENT;
+ app_id_.clear();
+ message_ = GCMClient::IncomingMessage();
+ send_error_details_ = GCMClient::SendErrorDetails();
+}
+
+} // namespace gcm
diff --git a/components/gcm_driver/fake_gcm_app_handler.h b/components/gcm_driver/fake_gcm_app_handler.h
new file mode 100644
index 0000000..fb98957
--- /dev/null
+++ b/components/gcm_driver/fake_gcm_app_handler.h
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_GCM_DRIVER_FAKE_GCM_APP_HANDLER_H_
+#define COMPONENTS_GCM_DRIVER_FAKE_GCM_APP_HANDLER_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/gcm_driver/gcm_app_handler.h"
+
+namespace base {
+class RunLoop;
+}
+
+namespace gcm {
+
+class FakeGCMAppHandler : public GCMAppHandler {
+ public:
+ enum Event {
+ NO_EVENT,
+ MESSAGE_EVENT,
+ MESSAGES_DELETED_EVENT,
+ SEND_ERROR_EVENT
+ };
+
+ FakeGCMAppHandler();
+ virtual ~FakeGCMAppHandler();
+
+ const Event& received_event() const { return received_event_; }
+ const std::string& app_id() const { return app_id_; }
+ const GCMClient::IncomingMessage& message() const { return message_; }
+ const GCMClient::SendErrorDetails& send_error_details() const {
+ return send_error_details_;
+ }
+
+ void WaitForNotification();
+
+ // GCMAppHandler:
+ virtual void ShutdownHandler() OVERRIDE;
+ virtual void OnMessage(const std::string& app_id,
+ const GCMClient::IncomingMessage& message) OVERRIDE;
+ virtual void OnMessagesDeleted(const std::string& app_id) OVERRIDE;
+ virtual void OnSendError(
+ const std::string& app_id,
+ const GCMClient::SendErrorDetails& send_error_details) OVERRIDE;
+
+ private:
+ void ClearResults();
+
+ scoped_ptr<base::RunLoop> run_loop_;
+
+ Event received_event_;
+ std::string app_id_;
+ GCMClient::IncomingMessage message_;
+ GCMClient::SendErrorDetails send_error_details_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeGCMAppHandler);
+};
+
+} // namespace gcm
+
+#endif // COMPONENTS_GCM_DRIVER_FAKE_GCM_APP_HANDLER_H_
diff --git a/components/gcm_driver/fake_gcm_client.cc b/components/gcm_driver/fake_gcm_client.cc
new file mode 100644
index 0000000..9898124
--- /dev/null
+++ b/components/gcm_driver/fake_gcm_client.cc
@@ -0,0 +1,221 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/gcm_driver/fake_gcm_client.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/sequenced_task_runner.h"
+#include "base/sys_byteorder.h"
+#include "base/time/time.h"
+#include "google_apis/gcm/base/encryptor.h"
+
+namespace gcm {
+
+FakeGCMClient::FakeGCMClient(
+ StartMode start_mode,
+ const scoped_refptr<base::SequencedTaskRunner>& ui_thread,
+ const scoped_refptr<base::SequencedTaskRunner>& io_thread)
+ : delegate_(NULL),
+ status_(UNINITIALIZED),
+ start_mode_(start_mode),
+ ui_thread_(ui_thread),
+ io_thread_(io_thread),
+ weak_ptr_factory_(this) {
+}
+
+FakeGCMClient::~FakeGCMClient() {
+}
+
+void FakeGCMClient::Initialize(
+ const ChromeBuildInfo& chrome_build_info,
+ const base::FilePath& store_path,
+ const std::vector<std::string>& account_ids,
+ const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner,
+ const scoped_refptr<net::URLRequestContextGetter>&
+ url_request_context_getter,
+ scoped_ptr<Encryptor> encryptor,
+ Delegate* delegate) {
+ delegate_ = delegate;
+}
+
+void FakeGCMClient::Start() {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+ DCHECK_NE(STARTED, status_);
+
+ if (start_mode_ == DELAY_START)
+ return;
+ DoLoading();
+}
+
+void FakeGCMClient::DoLoading() {
+ status_ = STARTED;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeGCMClient::CheckinFinished,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void FakeGCMClient::Stop() {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+ status_ = STOPPED;
+}
+
+void FakeGCMClient::CheckOut() {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+ status_ = CHECKED_OUT;
+}
+
+void FakeGCMClient::Register(const std::string& app_id,
+ const std::vector<std::string>& sender_ids) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ std::string registration_id = GetRegistrationIdFromSenderIds(sender_ids);
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeGCMClient::RegisterFinished,
+ weak_ptr_factory_.GetWeakPtr(),
+ app_id,
+ registration_id));
+}
+
+void FakeGCMClient::Unregister(const std::string& app_id) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeGCMClient::UnregisterFinished,
+ weak_ptr_factory_.GetWeakPtr(),
+ app_id));
+}
+
+void FakeGCMClient::Send(const std::string& app_id,
+ const std::string& receiver_id,
+ const OutgoingMessage& message) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeGCMClient::SendFinished,
+ weak_ptr_factory_.GetWeakPtr(),
+ app_id,
+ message));
+}
+
+void FakeGCMClient::SetRecording(bool recording) {
+}
+
+void FakeGCMClient::ClearActivityLogs() {
+}
+
+GCMClient::GCMStatistics FakeGCMClient::GetStatistics() const {
+ return GCMClient::GCMStatistics();
+}
+
+void FakeGCMClient::PerformDelayedLoading() {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeGCMClient::DoLoading, weak_ptr_factory_.GetWeakPtr()));
+}
+
+void FakeGCMClient::ReceiveMessage(const std::string& app_id,
+ const IncomingMessage& message) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeGCMClient::MessageReceived,
+ weak_ptr_factory_.GetWeakPtr(),
+ app_id,
+ message));
+}
+
+void FakeGCMClient::DeleteMessages(const std::string& app_id) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeGCMClient::MessagesDeleted,
+ weak_ptr_factory_.GetWeakPtr(),
+ app_id));
+}
+
+// static
+std::string FakeGCMClient::GetRegistrationIdFromSenderIds(
+ const std::vector<std::string>& sender_ids) {
+ // GCMService normalizes the sender IDs by making them sorted.
+ std::vector<std::string> normalized_sender_ids = sender_ids;
+ std::sort(normalized_sender_ids.begin(), normalized_sender_ids.end());
+
+ // Simulate the registration_id by concaternating all sender IDs.
+ // Set registration_id to empty to denote an error if sender_ids contains a
+ // hint.
+ std::string registration_id;
+ if (sender_ids.size() != 1 ||
+ sender_ids[0].find("error") == std::string::npos) {
+ for (size_t i = 0; i < normalized_sender_ids.size(); ++i) {
+ if (i > 0)
+ registration_id += ",";
+ registration_id += normalized_sender_ids[i];
+ }
+ }
+ return registration_id;
+}
+
+void FakeGCMClient::CheckinFinished() {
+ delegate_->OnGCMReady();
+}
+
+void FakeGCMClient::RegisterFinished(const std::string& app_id,
+ const std::string& registrion_id) {
+ delegate_->OnRegisterFinished(
+ app_id, registrion_id, registrion_id.empty() ? SERVER_ERROR : SUCCESS);
+}
+
+void FakeGCMClient::UnregisterFinished(const std::string& app_id) {
+ delegate_->OnUnregisterFinished(app_id, GCMClient::SUCCESS);
+}
+
+void FakeGCMClient::SendFinished(const std::string& app_id,
+ const OutgoingMessage& message) {
+ delegate_->OnSendFinished(app_id, message.id, SUCCESS);
+
+ // Simulate send error if message id contains a hint.
+ if (message.id.find("error") != std::string::npos) {
+ SendErrorDetails send_error_details;
+ send_error_details.message_id = message.id;
+ send_error_details.result = NETWORK_ERROR;
+ send_error_details.additional_data = message.data;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&FakeGCMClient::MessageSendError,
+ weak_ptr_factory_.GetWeakPtr(),
+ app_id,
+ send_error_details),
+ base::TimeDelta::FromMilliseconds(200));
+ }
+}
+
+void FakeGCMClient::MessageReceived(const std::string& app_id,
+ const IncomingMessage& message) {
+ if (delegate_)
+ delegate_->OnMessageReceived(app_id, message);
+}
+
+void FakeGCMClient::MessagesDeleted(const std::string& app_id) {
+ if (delegate_)
+ delegate_->OnMessagesDeleted(app_id);
+}
+
+void FakeGCMClient::MessageSendError(
+ const std::string& app_id,
+ const GCMClient::SendErrorDetails& send_error_details) {
+ if (delegate_)
+ delegate_->OnMessageSendError(app_id, send_error_details);
+}
+
+} // namespace gcm
diff --git a/components/gcm_driver/fake_gcm_client.h b/components/gcm_driver/fake_gcm_client.h
new file mode 100644
index 0000000..a1d4f2b
--- /dev/null
+++ b/components/gcm_driver/fake_gcm_client.h
@@ -0,0 +1,102 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_GCM_DRIVER_FAKE_GCM_CLIENT_H_
+#define COMPONENTS_GCM_DRIVER_FAKE_GCM_CLIENT_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "google_apis/gcm/gcm_client.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace gcm {
+
+class FakeGCMClient : public GCMClient {
+ public:
+ enum Status {
+ UNINITIALIZED,
+ STARTED,
+ STOPPED,
+ CHECKED_OUT
+ };
+
+ enum StartMode {
+ NO_DELAY_START,
+ DELAY_START,
+ };
+
+ FakeGCMClient(StartMode start_mode,
+ const scoped_refptr<base::SequencedTaskRunner>& ui_thread,
+ const scoped_refptr<base::SequencedTaskRunner>& io_thread);
+ virtual ~FakeGCMClient();
+
+ // Overridden from GCMClient:
+ // Called on IO thread.
+ virtual void Initialize(
+ const ChromeBuildInfo& chrome_build_info,
+ const base::FilePath& store_path,
+ const std::vector<std::string>& account_ids,
+ const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner,
+ const scoped_refptr<net::URLRequestContextGetter>&
+ url_request_context_getter,
+ scoped_ptr<Encryptor> encryptor,
+ Delegate* delegate) OVERRIDE;
+ virtual void Start() OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual void CheckOut() OVERRIDE;
+ virtual void Register(const std::string& app_id,
+ const std::vector<std::string>& sender_ids) OVERRIDE;
+ virtual void Unregister(const std::string& app_id) OVERRIDE;
+ virtual void Send(const std::string& app_id,
+ const std::string& receiver_id,
+ const OutgoingMessage& message) OVERRIDE;
+ virtual void SetRecording(bool recording) OVERRIDE;
+ virtual void ClearActivityLogs() OVERRIDE;
+ virtual GCMStatistics GetStatistics() const OVERRIDE;
+
+ // Initiate the loading that has been delayed.
+ // Called on UI thread.
+ void PerformDelayedLoading();
+
+ // Simulate receiving something from the server.
+ // Called on UI thread.
+ void ReceiveMessage(const std::string& app_id,
+ const IncomingMessage& message);
+ void DeleteMessages(const std::string& app_id);
+
+ static std::string GetRegistrationIdFromSenderIds(
+ const std::vector<std::string>& sender_ids);
+
+ Status status() const { return status_; }
+
+ private:
+ // Called on IO thread.
+ void DoLoading();
+ void CheckinFinished();
+ void RegisterFinished(const std::string& app_id,
+ const std::string& registrion_id);
+ void UnregisterFinished(const std::string& app_id);
+ void SendFinished(const std::string& app_id, const OutgoingMessage& message);
+ void MessageReceived(const std::string& app_id,
+ const IncomingMessage& message);
+ void MessagesDeleted(const std::string& app_id);
+ void MessageSendError(const std::string& app_id,
+ const SendErrorDetails& send_error_details);
+
+ Delegate* delegate_;
+ Status status_;
+ StartMode start_mode_;
+ scoped_refptr<base::SequencedTaskRunner> ui_thread_;
+ scoped_refptr<base::SequencedTaskRunner> io_thread_;
+ base::WeakPtrFactory<FakeGCMClient> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeGCMClient);
+};
+
+} // namespace gcm
+
+#endif // COMPONENTS_GCM_DRIVER_FAKE_GCM_CLIENT_H_
diff --git a/components/gcm_driver/fake_gcm_client_factory.cc b/components/gcm_driver/fake_gcm_client_factory.cc
new file mode 100644
index 0000000..e071302
--- /dev/null
+++ b/components/gcm_driver/fake_gcm_client_factory.cc
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/gcm_driver/fake_gcm_client_factory.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/sequenced_task_runner.h"
+#include "google_apis/gcm/gcm_client.h"
+
+namespace gcm {
+
+FakeGCMClientFactory::FakeGCMClientFactory(
+ FakeGCMClient::StartMode gcm_client_start_mode,
+ const scoped_refptr<base::SequencedTaskRunner>& ui_thread,
+ const scoped_refptr<base::SequencedTaskRunner>& io_thread)
+ : gcm_client_start_mode_(gcm_client_start_mode),
+ ui_thread_(ui_thread),
+ io_thread_(io_thread) {
+}
+
+FakeGCMClientFactory::~FakeGCMClientFactory() {
+}
+
+scoped_ptr<GCMClient> FakeGCMClientFactory::BuildInstance() {
+ return scoped_ptr<GCMClient>(new FakeGCMClient(
+ gcm_client_start_mode_, ui_thread_, io_thread_));
+}
+
+} // namespace gcm
diff --git a/components/gcm_driver/fake_gcm_client_factory.h b/components/gcm_driver/fake_gcm_client_factory.h
new file mode 100644
index 0000000..a4b2d84
--- /dev/null
+++ b/components/gcm_driver/fake_gcm_client_factory.h
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_GCM_DRIVER_FAKE_GCM_CLIENT_FACTORY_H_
+#define COMPONENTS_GCM_DRIVER_FAKE_GCM_CLIENT_FACTORY_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "components/gcm_driver/fake_gcm_client.h"
+#include "components/gcm_driver/gcm_client_factory.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace gcm {
+
+class GCMClient;
+
+class FakeGCMClientFactory : public GCMClientFactory {
+ public:
+ FakeGCMClientFactory(
+ FakeGCMClient::StartMode gcm_client_start_mode,
+ const scoped_refptr<base::SequencedTaskRunner>& ui_thread,
+ const scoped_refptr<base::SequencedTaskRunner>& io_thread);
+ virtual ~FakeGCMClientFactory();
+
+ // GCMClientFactory:
+ virtual scoped_ptr<GCMClient> BuildInstance() OVERRIDE;
+
+ private:
+ FakeGCMClient::StartMode gcm_client_start_mode_;
+ scoped_refptr<base::SequencedTaskRunner> ui_thread_;
+ scoped_refptr<base::SequencedTaskRunner> io_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeGCMClientFactory);
+};
+
+} // namespace gcm
+
+#endif // COMPONENTS_GCM_DRIVER_FAKE_GCM_CLIENT_FACTORY_H_
diff --git a/components/gcm_driver/gcm_driver.cc b/components/gcm_driver/gcm_driver.cc
new file mode 100644
index 0000000..e41972e
--- /dev/null
+++ b/components/gcm_driver/gcm_driver.cc
@@ -0,0 +1,821 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/gcm_driver/gcm_driver.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "components/gcm_driver/gcm_app_handler.h"
+#include "components/gcm_driver/gcm_client_factory.h"
+#include "components/gcm_driver/system_encryptor.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace gcm {
+
+// Helper class to save tasks to run until we're ready to execute them.
+class GCMDriver::DelayedTaskController {
+ public:
+ DelayedTaskController();
+ ~DelayedTaskController();
+
+ // Adds a task that will be invoked once we're ready.
+ void AddTask(const base::Closure& task);
+
+ // Sets ready status. It is ready only when check-in is completed and
+ // the GCMClient is fully initialized.
+ void SetReady();
+
+ // Returns true if it is ready to perform tasks.
+ bool CanRunTaskWithoutDelay() const;
+
+ private:
+ void RunTasks();
+
+ // Flag that indicates that GCM is ready.
+ bool ready_;
+
+ std::vector<base::Closure> delayed_tasks_;
+
+ DISALLOW_COPY_AND_ASSIGN(DelayedTaskController);
+};
+
+GCMDriver::DelayedTaskController::DelayedTaskController() : ready_(false) {
+}
+
+GCMDriver::DelayedTaskController::~DelayedTaskController() {
+}
+
+void GCMDriver::DelayedTaskController::AddTask(const base::Closure& task) {
+ delayed_tasks_.push_back(task);
+}
+
+void GCMDriver::DelayedTaskController::SetReady() {
+ ready_ = true;
+ RunTasks();
+}
+
+bool GCMDriver::DelayedTaskController::CanRunTaskWithoutDelay() const {
+ return ready_;
+}
+
+void GCMDriver::DelayedTaskController::RunTasks() {
+ DCHECK(ready_);
+
+ for (size_t i = 0; i < delayed_tasks_.size(); ++i)
+ delayed_tasks_[i].Run();
+ delayed_tasks_.clear();
+}
+
+class GCMDriver::IOWorker : public GCMClient::Delegate {
+ public:
+ // Called on UI thread.
+ IOWorker(const scoped_refptr<base::SequencedTaskRunner>& ui_thread,
+ const scoped_refptr<base::SequencedTaskRunner>& io_thread);
+ virtual ~IOWorker();
+
+ // Overridden from GCMClient::Delegate:
+ // Called on IO thread.
+ virtual void OnRegisterFinished(const std::string& app_id,
+ const std::string& registration_id,
+ GCMClient::Result result) OVERRIDE;
+ virtual void OnUnregisterFinished(const std::string& app_id,
+ GCMClient::Result result) OVERRIDE;
+ virtual void OnSendFinished(const std::string& app_id,
+ const std::string& message_id,
+ GCMClient::Result result) OVERRIDE;
+ virtual void OnMessageReceived(
+ const std::string& app_id,
+ const GCMClient::IncomingMessage& message) OVERRIDE;
+ virtual void OnMessagesDeleted(const std::string& app_id) OVERRIDE;
+ virtual void OnMessageSendError(
+ const std::string& app_id,
+ const GCMClient::SendErrorDetails& send_error_details) OVERRIDE;
+ virtual void OnGCMReady() OVERRIDE;
+ virtual void OnActivityRecorded() OVERRIDE;
+
+ // Called on IO thread.
+ void Initialize(
+ scoped_ptr<GCMClientFactory> gcm_client_factory,
+ const GCMClient::ChromeBuildInfo& chrome_build_info,
+ const base::FilePath& store_path,
+ const std::vector<std::string>& account_ids,
+ const scoped_refptr<net::URLRequestContextGetter>& request_context,
+ const scoped_refptr<base::SequencedTaskRunner> blocking_task_runner);
+ void Start(const base::WeakPtr<GCMDriver>& service);
+ void Stop();
+ void CheckOut();
+ void Register(const std::string& app_id,
+ const std::vector<std::string>& sender_ids);
+ void Unregister(const std::string& app_id);
+ void Send(const std::string& app_id,
+ const std::string& receiver_id,
+ const GCMClient::OutgoingMessage& message);
+ void GetGCMStatistics(bool clear_logs);
+ void SetGCMRecording(bool recording);
+
+ // For testing purpose. Can be called from UI thread. Use with care.
+ GCMClient* gcm_client_for_testing() const { return gcm_client_.get(); }
+
+ private:
+ scoped_refptr<base::SequencedTaskRunner> ui_thread_;
+ scoped_refptr<base::SequencedTaskRunner> io_thread_;
+
+ base::WeakPtr<GCMDriver> service_;
+
+ scoped_ptr<GCMClient> gcm_client_;
+
+ DISALLOW_COPY_AND_ASSIGN(IOWorker);
+};
+
+GCMDriver::IOWorker::IOWorker(
+ const scoped_refptr<base::SequencedTaskRunner>& ui_thread,
+ const scoped_refptr<base::SequencedTaskRunner>& io_thread)
+ : ui_thread_(ui_thread),
+ io_thread_(io_thread) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+}
+
+GCMDriver::IOWorker::~IOWorker() {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+}
+
+void GCMDriver::IOWorker::Initialize(
+ scoped_ptr<GCMClientFactory> gcm_client_factory,
+ const GCMClient::ChromeBuildInfo& chrome_build_info,
+ const base::FilePath& store_path,
+ const std::vector<std::string>& account_ids,
+ const scoped_refptr<net::URLRequestContextGetter>& request_context,
+ const scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ gcm_client_ = gcm_client_factory->BuildInstance();
+
+ gcm_client_->Initialize(chrome_build_info,
+ store_path,
+ account_ids,
+ blocking_task_runner,
+ request_context,
+ make_scoped_ptr<Encryptor>(new SystemEncryptor),
+ this);
+}
+
+void GCMDriver::IOWorker::OnRegisterFinished(
+ const std::string& app_id,
+ const std::string& registration_id,
+ GCMClient::Result result) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ ui_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::RegisterFinished, service_, app_id,
+ registration_id, result));
+}
+
+void GCMDriver::IOWorker::OnUnregisterFinished(const std::string& app_id,
+ GCMClient::Result result) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ ui_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::UnregisterFinished, service_, app_id, result));
+}
+
+void GCMDriver::IOWorker::OnSendFinished(const std::string& app_id,
+ const std::string& message_id,
+ GCMClient::Result result) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ ui_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::SendFinished, service_, app_id, message_id,
+ result));
+}
+
+void GCMDriver::IOWorker::OnMessageReceived(
+ const std::string& app_id,
+ const GCMClient::IncomingMessage& message) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ ui_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::MessageReceived, service_, app_id, message));
+}
+
+void GCMDriver::IOWorker::OnMessagesDeleted(const std::string& app_id) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ ui_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::MessagesDeleted, service_, app_id));
+}
+
+void GCMDriver::IOWorker::OnMessageSendError(
+ const std::string& app_id,
+ const GCMClient::SendErrorDetails& send_error_details) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ ui_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::MessageSendError, service_, app_id,
+ send_error_details));
+}
+
+void GCMDriver::IOWorker::OnGCMReady() {
+ ui_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::GCMClientReady, service_));
+}
+
+void GCMDriver::IOWorker::OnActivityRecorded() {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+ // When an activity is recorded, get all the stats and refresh the UI of
+ // gcm-internals page.
+ GetGCMStatistics(false);
+}
+
+void GCMDriver::IOWorker::Start(const base::WeakPtr<GCMDriver>& service) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ service_ = service;
+ gcm_client_->Start();
+}
+
+void GCMDriver::IOWorker::Stop() {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ gcm_client_->Stop();
+}
+
+void GCMDriver::IOWorker::CheckOut() {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ gcm_client_->CheckOut();
+
+ // Note that we still need to keep GCMClient instance alive since the
+ // GCMDriver may check in again.
+}
+
+void GCMDriver::IOWorker::Register(
+ const std::string& app_id,
+ const std::vector<std::string>& sender_ids) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ gcm_client_->Register(app_id, sender_ids);
+}
+
+void GCMDriver::IOWorker::Unregister(const std::string& app_id) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ gcm_client_->Unregister(app_id);
+}
+
+void GCMDriver::IOWorker::Send(const std::string& app_id,
+ const std::string& receiver_id,
+ const GCMClient::OutgoingMessage& message) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+
+ gcm_client_->Send(app_id, receiver_id, message);
+}
+
+void GCMDriver::IOWorker::GetGCMStatistics(bool clear_logs) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+ gcm::GCMClient::GCMStatistics stats;
+
+ if (gcm_client_.get()) {
+ if (clear_logs)
+ gcm_client_->ClearActivityLogs();
+ stats = gcm_client_->GetStatistics();
+ }
+
+ ui_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::GetGCMStatisticsFinished, service_, stats));
+}
+
+void GCMDriver::IOWorker::SetGCMRecording(bool recording) {
+ DCHECK(io_thread_->RunsTasksOnCurrentThread());
+ gcm::GCMClient::GCMStatistics stats;
+
+ if (gcm_client_.get()) {
+ gcm_client_->SetRecording(recording);
+ stats = gcm_client_->GetStatistics();
+ stats.gcm_client_created = true;
+ }
+
+ ui_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::GetGCMStatisticsFinished, service_, stats));
+}
+
+GCMDriver::GCMDriver(
+ scoped_ptr<GCMClientFactory> gcm_client_factory,
+ scoped_ptr<IdentityProvider> identity_provider,
+ const GCMClient::ChromeBuildInfo& chrome_build_info,
+ const base::FilePath& store_path,
+ const scoped_refptr<net::URLRequestContextGetter>& request_context,
+ const scoped_refptr<base::SequencedTaskRunner>& ui_thread,
+ const scoped_refptr<base::SequencedTaskRunner>& io_thread,
+ const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner)
+ : gcm_enabled_(true),
+ gcm_client_ready_(false),
+ identity_provider_(identity_provider.Pass()),
+ ui_thread_(ui_thread),
+ io_thread_(io_thread),
+ weak_ptr_factory_(this) {
+ // Get the list of available accounts.
+ std::vector<std::string> account_ids;
+#if !defined(OS_ANDROID)
+ account_ids = identity_provider_->GetTokenService()->GetAccounts();
+#endif
+
+ // Create and initialize the GCMClient. Note that this does not initiate the
+ // GCM check-in.
+ io_worker_.reset(new IOWorker(ui_thread, io_thread));
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::IOWorker::Initialize,
+ base::Unretained(io_worker_.get()),
+ base::Passed(&gcm_client_factory),
+ chrome_build_info,
+ store_path,
+ account_ids,
+ request_context,
+ blocking_task_runner));
+
+ identity_provider_->AddObserver(this);
+}
+
+GCMDriver::GCMDriver()
+ : gcm_enabled_(true),
+ gcm_client_ready_(false),
+ weak_ptr_factory_(this) {
+}
+
+GCMDriver::~GCMDriver() {
+}
+
+void GCMDriver::Enable() {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ if (gcm_enabled_)
+ return;
+ gcm_enabled_ = true;
+
+ EnsureStarted();
+}
+
+void GCMDriver::Disable() {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ if (!gcm_enabled_)
+ return;
+ gcm_enabled_ = false;
+
+ Stop();
+}
+
+void GCMDriver::Stop() {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ // No need to stop GCM service if not started yet.
+ if (account_id_.empty())
+ return;
+
+ RemoveCachedData();
+
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::IOWorker::Stop,
+ base::Unretained(io_worker_.get())));
+}
+
+void GCMDriver::Shutdown() {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+ identity_provider_->RemoveObserver(this);
+ for (GCMAppHandlerMap::const_iterator iter = app_handlers_.begin();
+ iter != app_handlers_.end(); ++iter) {
+ iter->second->ShutdownHandler();
+ }
+ app_handlers_.clear();
+ io_thread_->DeleteSoon(FROM_HERE, io_worker_.release());
+}
+
+void GCMDriver::AddAppHandler(const std::string& app_id,
+ GCMAppHandler* handler) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+ DCHECK(!app_id.empty());
+ DCHECK(handler);
+ DCHECK(app_handlers_.find(app_id) == app_handlers_.end());
+
+ app_handlers_[app_id] = handler;
+
+ // Ensures that the GCM service is started when there is an interest.
+ EnsureStarted();
+}
+
+void GCMDriver::RemoveAppHandler(const std::string& app_id) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+ DCHECK(!app_id.empty());
+
+ app_handlers_.erase(app_id);
+
+ // Stops the GCM service when no app intends to consume it.
+ if (app_handlers_.empty())
+ Stop();
+}
+
+void GCMDriver::Register(const std::string& app_id,
+ const std::vector<std::string>& sender_ids,
+ const RegisterCallback& callback) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+ DCHECK(!app_id.empty());
+ DCHECK(!sender_ids.empty());
+ DCHECK(!callback.is_null());
+
+ GCMClient::Result result = EnsureStarted();
+ if (result != GCMClient::SUCCESS) {
+ callback.Run(std::string(), result);
+ return;
+ }
+
+ // If previous un/register operation is still in progress, bail out.
+ if (IsAsyncOperationPending(app_id)) {
+ callback.Run(std::string(), GCMClient::ASYNC_OPERATION_PENDING);
+ return;
+ }
+
+ register_callbacks_[app_id] = callback;
+
+ // Delay the register operation until GCMClient is ready.
+ if (!delayed_task_controller_->CanRunTaskWithoutDelay()) {
+ delayed_task_controller_->AddTask(base::Bind(&GCMDriver::DoRegister,
+ weak_ptr_factory_.GetWeakPtr(),
+ app_id,
+ sender_ids));
+ return;
+ }
+
+ DoRegister(app_id, sender_ids);
+}
+
+void GCMDriver::DoRegister(const std::string& app_id,
+ const std::vector<std::string>& sender_ids) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+ std::map<std::string, RegisterCallback>::iterator callback_iter =
+ register_callbacks_.find(app_id);
+ if (callback_iter == register_callbacks_.end()) {
+ // The callback could have been removed when the app is uninstalled.
+ return;
+ }
+
+ // Normalize the sender IDs by making them sorted.
+ std::vector<std::string> normalized_sender_ids = sender_ids;
+ std::sort(normalized_sender_ids.begin(), normalized_sender_ids.end());
+
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::IOWorker::Register,
+ base::Unretained(io_worker_.get()),
+ app_id,
+ normalized_sender_ids));
+}
+
+void GCMDriver::Unregister(const std::string& app_id,
+ const UnregisterCallback& callback) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+ DCHECK(!app_id.empty());
+ DCHECK(!callback.is_null());
+
+ GCMClient::Result result = EnsureStarted();
+ if (result != GCMClient::SUCCESS) {
+ callback.Run(result);
+ return;
+ }
+
+ // If previous un/register operation is still in progress, bail out.
+ if (IsAsyncOperationPending(app_id)) {
+ callback.Run(GCMClient::ASYNC_OPERATION_PENDING);
+ return;
+ }
+
+ unregister_callbacks_[app_id] = callback;
+
+ // Delay the unregister operation until GCMClient is ready.
+ if (!delayed_task_controller_->CanRunTaskWithoutDelay()) {
+ delayed_task_controller_->AddTask(base::Bind(&GCMDriver::DoUnregister,
+ weak_ptr_factory_.GetWeakPtr(),
+ app_id));
+ return;
+ }
+
+ DoUnregister(app_id);
+}
+
+void GCMDriver::DoUnregister(const std::string& app_id) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ // Ask the server to unregister it. There could be a small chance that the
+ // unregister request fails. If this occurs, it does not bring any harm since
+ // we simply reject the messages/events received from the server.
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::IOWorker::Unregister,
+ base::Unretained(io_worker_.get()),
+ app_id));
+}
+
+void GCMDriver::Send(const std::string& app_id,
+ const std::string& receiver_id,
+ const GCMClient::OutgoingMessage& message,
+ const SendCallback& callback) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+ DCHECK(!app_id.empty());
+ DCHECK(!receiver_id.empty());
+ DCHECK(!callback.is_null());
+
+ GCMClient::Result result = EnsureStarted();
+ if (result != GCMClient::SUCCESS) {
+ callback.Run(std::string(), result);
+ return;
+ }
+
+ // If the message with send ID is still in progress, bail out.
+ std::pair<std::string, std::string> key(app_id, message.id);
+ if (send_callbacks_.find(key) != send_callbacks_.end()) {
+ callback.Run(message.id, GCMClient::INVALID_PARAMETER);
+ return;
+ }
+
+ send_callbacks_[key] = callback;
+
+ // Delay the send operation until all GCMClient is ready.
+ if (!delayed_task_controller_->CanRunTaskWithoutDelay()) {
+ delayed_task_controller_->AddTask(base::Bind(&GCMDriver::DoSend,
+ weak_ptr_factory_.GetWeakPtr(),
+ app_id,
+ receiver_id,
+ message));
+ return;
+ }
+
+ DoSend(app_id, receiver_id, message);
+}
+
+void GCMDriver::DoSend(const std::string& app_id,
+ const std::string& receiver_id,
+ const GCMClient::OutgoingMessage& message) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::IOWorker::Send,
+ base::Unretained(io_worker_.get()),
+ app_id,
+ receiver_id,
+ message));
+}
+
+GCMClient* GCMDriver::GetGCMClientForTesting() const {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+ return io_worker_ ? io_worker_->gcm_client_for_testing() : NULL;
+}
+
+bool GCMDriver::IsStarted() const {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+ return !account_id_.empty();
+}
+
+bool GCMDriver::IsGCMClientReady() const {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+ return gcm_client_ready_;
+}
+
+void GCMDriver::GetGCMStatistics(const GetGCMStatisticsCallback& callback,
+ bool clear_logs) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+ DCHECK(!callback.is_null());
+
+ request_gcm_statistics_callback_ = callback;
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::IOWorker::GetGCMStatistics,
+ base::Unretained(io_worker_.get()),
+ clear_logs));
+}
+
+void GCMDriver::SetGCMRecording(const GetGCMStatisticsCallback& callback,
+ bool recording) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ request_gcm_statistics_callback_ = callback;
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::IOWorker::SetGCMRecording,
+ base::Unretained(io_worker_.get()),
+ recording));
+}
+
+void GCMDriver::OnActiveAccountLogin() {
+ EnsureStarted();
+}
+
+void GCMDriver::OnActiveAccountLogout() {
+ CheckOut();
+}
+
+GCMClient::Result GCMDriver::EnsureStarted() {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ if (!gcm_enabled_)
+ return GCMClient::GCM_DISABLED;
+
+ // Have any app requested the service?
+ if (app_handlers_.empty())
+ return GCMClient::UNKNOWN_ERROR;
+
+ // Is the user signed in?
+ const std::string account_id = identity_provider_->GetActiveAccountId();
+ if (account_id.empty())
+ return GCMClient::NOT_SIGNED_IN;
+
+ // CheckIn could be called more than once when:
+ // 1) The password changes.
+ // 2) Register/send function calls it to ensure CheckIn is done.
+ if (account_id_ == account_id)
+ return GCMClient::SUCCESS;
+ account_id_ = account_id;
+
+ DCHECK(!delayed_task_controller_);
+ delayed_task_controller_.reset(new DelayedTaskController);
+
+ // Note that we need to pass weak pointer again since the existing weak
+ // pointer in IOWorker might have been invalidated when check-out occurs.
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::IOWorker::Start,
+ base::Unretained(io_worker_.get()),
+ weak_ptr_factory_.GetWeakPtr()));
+
+ return GCMClient::SUCCESS;
+}
+
+void GCMDriver::RemoveCachedData() {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+ // Remove all the queued tasks since they no longer make sense after
+ // GCM service is stopped.
+ weak_ptr_factory_.InvalidateWeakPtrs();
+
+ account_id_.clear();
+ gcm_client_ready_ = false;
+ delayed_task_controller_.reset();
+ register_callbacks_.clear();
+ send_callbacks_.clear();
+}
+
+void GCMDriver::CheckOut() {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ // We still proceed with the check-out logic even if the check-in is not
+ // initiated in the current session. This will make sure that all the
+ // persisted data written previously will get purged.
+
+ RemoveCachedData();
+
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMDriver::IOWorker::CheckOut,
+ base::Unretained(io_worker_.get())));
+}
+
+bool GCMDriver::IsAsyncOperationPending(const std::string& app_id) const {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+ return register_callbacks_.find(app_id) != register_callbacks_.end() ||
+ unregister_callbacks_.find(app_id) != unregister_callbacks_.end();
+}
+
+void GCMDriver::RegisterFinished(const std::string& app_id,
+ const std::string& registration_id,
+ GCMClient::Result result) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ std::map<std::string, RegisterCallback>::iterator callback_iter =
+ register_callbacks_.find(app_id);
+ if (callback_iter == register_callbacks_.end()) {
+ // The callback could have been removed when the app is uninstalled.
+ return;
+ }
+
+ RegisterCallback callback = callback_iter->second;
+ register_callbacks_.erase(callback_iter);
+ callback.Run(registration_id, result);
+}
+
+void GCMDriver::UnregisterFinished(const std::string& app_id,
+ GCMClient::Result result) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ std::map<std::string, UnregisterCallback>::iterator callback_iter =
+ unregister_callbacks_.find(app_id);
+ if (callback_iter == unregister_callbacks_.end())
+ return;
+
+ UnregisterCallback callback = callback_iter->second;
+ unregister_callbacks_.erase(callback_iter);
+ callback.Run(result);
+}
+
+void GCMDriver::SendFinished(const std::string& app_id,
+ const std::string& message_id,
+ GCMClient::Result result) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ std::map<std::pair<std::string, std::string>, SendCallback>::iterator
+ callback_iter = send_callbacks_.find(
+ std::pair<std::string, std::string>(app_id, message_id));
+ if (callback_iter == send_callbacks_.end()) {
+ // The callback could have been removed when the app is uninstalled.
+ return;
+ }
+
+ SendCallback callback = callback_iter->second;
+ send_callbacks_.erase(callback_iter);
+ callback.Run(message_id, result);
+}
+
+void GCMDriver::MessageReceived(const std::string& app_id,
+ GCMClient::IncomingMessage message) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ // Drop the event if signed out.
+ if (account_id_.empty())
+ return;
+
+ GetAppHandler(app_id)->OnMessage(app_id, message);
+}
+
+void GCMDriver::MessagesDeleted(const std::string& app_id) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ // Drop the event if signed out.
+ if (account_id_.empty())
+ return;
+
+ GetAppHandler(app_id)->OnMessagesDeleted(app_id);
+}
+
+void GCMDriver::MessageSendError(
+ const std::string& app_id,
+ const GCMClient::SendErrorDetails& send_error_details) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ // Drop the event if signed out.
+ if (account_id_.empty())
+ return;
+
+ GetAppHandler(app_id)->OnSendError(app_id, send_error_details);
+}
+
+void GCMDriver::GCMClientReady() {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ if (gcm_client_ready_)
+ return;
+ gcm_client_ready_ = true;
+
+ delayed_task_controller_->SetReady();
+}
+
+GCMAppHandler* GCMDriver::GetAppHandler(const std::string& app_id) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ std::map<std::string, GCMAppHandler*>::const_iterator iter =
+ app_handlers_.find(app_id);
+ return iter == app_handlers_.end() ? &default_app_handler_ : iter->second;
+}
+
+void GCMDriver::GetGCMStatisticsFinished(GCMClient::GCMStatistics stats) {
+ DCHECK(ui_thread_->RunsTasksOnCurrentThread());
+
+ // Normally request_gcm_statistics_callback_ would not be null.
+ if (!request_gcm_statistics_callback_.is_null())
+ request_gcm_statistics_callback_.Run(stats);
+ else
+ LOG(WARNING) << "request_gcm_statistics_callback_ is NULL.";
+}
+
+std::string GCMDriver::SignedInUserName() const {
+ if (IsStarted())
+ return identity_provider_->GetActiveUsername();
+ return std::string();
+}
+
+} // namespace gcm
diff --git a/components/gcm_driver/gcm_driver.h b/components/gcm_driver/gcm_driver.h
new file mode 100644
index 0000000..897dbfa
--- /dev/null
+++ b/components/gcm_driver/gcm_driver.h
@@ -0,0 +1,233 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_GCM_DRIVER_GCM_DRIVER_H_
+#define COMPONENTS_GCM_DRIVER_GCM_DRIVER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "components/gcm_driver/default_gcm_app_handler.h"
+#include "google_apis/gaia/identity_provider.h"
+#include "google_apis/gcm/gcm_client.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+}
+
+namespace extensions {
+class ExtensionGCMAppHandlerTest;
+}
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace gcm {
+
+class GCMAppHandler;
+class GCMClientFactory;
+
+// A bridge between the GCM users in Chrome and the GCMClient layer.
+class GCMDriver : public IdentityProvider::Observer {
+ public:
+ typedef std::map<std::string, GCMAppHandler*> GCMAppHandlerMap;
+ typedef base::Callback<void(const std::string& registration_id,
+ GCMClient::Result result)> RegisterCallback;
+ typedef base::Callback<void(const std::string& message_id,
+ GCMClient::Result result)> SendCallback;
+ typedef base::Callback<void(GCMClient::Result result)> UnregisterCallback;
+ typedef base::Callback<void(const GCMClient::GCMStatistics& stats)>
+ GetGCMStatisticsCallback;
+
+ GCMDriver(
+ scoped_ptr<GCMClientFactory> gcm_client_factory,
+ scoped_ptr<IdentityProvider> identity_provider,
+ const GCMClient::ChromeBuildInfo& chrome_build_info,
+ const base::FilePath& store_path,
+ const scoped_refptr<net::URLRequestContextGetter>& request_context,
+ const scoped_refptr<base::SequencedTaskRunner>& ui_thread,
+ const scoped_refptr<base::SequencedTaskRunner>& io_thread,
+ const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner);
+ virtual ~GCMDriver();
+
+ // Enables/disables GCM service.
+ void Enable();
+ void Disable();
+
+ // This method must be called before destroying the GCMDriver. Once it has
+ // been called, no other GCMDriver methods may be used.
+ virtual void Shutdown();
+
+ // Adds a handler for a given app.
+ virtual void AddAppHandler(const std::string& app_id, GCMAppHandler* handler);
+
+ // Remove the handler for a given app.
+ virtual void RemoveAppHandler(const std::string& app_id);
+
+ // Registers |sender_id| for an app. A registration ID will be returned by
+ // the GCM server.
+ // |app_id|: application ID.
+ // |sender_ids|: list of IDs of the servers that are allowed to send the
+ // messages to the application. These IDs are assigned by the
+ // Google API Console.
+ // |callback|: to be called once the asynchronous operation is done.
+ virtual void Register(const std::string& app_id,
+ const std::vector<std::string>& sender_ids,
+ const RegisterCallback& callback);
+
+ // Unregisters an app from using GCM.
+ // |app_id|: application ID.
+ // |callback|: to be called once the asynchronous operation is done.
+ virtual void Unregister(const std::string& app_id,
+ const UnregisterCallback& callback);
+
+ // Sends a message to a given receiver.
+ // |app_id|: application ID.
+ // |receiver_id|: registration ID of the receiver party.
+ // |message|: message to be sent.
+ // |callback|: to be called once the asynchronous operation is done.
+ virtual void Send(const std::string& app_id,
+ const std::string& receiver_id,
+ const GCMClient::OutgoingMessage& message,
+ const SendCallback& callback);
+
+ // For testing purpose.
+ GCMClient* GetGCMClientForTesting() const;
+
+ // Returns true if the service was started.
+ bool IsStarted() const;
+
+ // Returns true if the gcm client is ready.
+ bool IsGCMClientReady() const;
+
+ // Get GCM client internal states and statistics.
+ // If clear_logs is true then activity logs will be cleared before the stats
+ // are returned.
+ void GetGCMStatistics(const GetGCMStatisticsCallback& callback,
+ bool clear_logs);
+
+ // Enables/disables GCM activity recording, and then returns the stats.
+ void SetGCMRecording(const GetGCMStatisticsCallback& callback,
+ bool recording);
+
+ // Returns the user name if the profile is signed in. Empty string otherwise.
+ std::string SignedInUserName() const;
+
+ // IdentityProvider::Observer:
+ virtual void OnActiveAccountLogin() OVERRIDE;
+ virtual void OnActiveAccountLogout() OVERRIDE;
+
+ const GCMAppHandlerMap& app_handlers() const { return app_handlers_; }
+
+ protected:
+ // Used for constructing fake GCMDriver for testing purpose.
+ GCMDriver();
+
+ private:
+ class DelayedTaskController;
+ class IOWorker;
+
+ // Ensures that the GCM service starts when all of the following conditions
+ // satisfy:
+ // 1) GCM is enabled.
+ // 2) The identity provider is able to supply an account ID.
+ GCMClient::Result EnsureStarted();
+
+ // Stops the GCM service. It can be restarted by calling EnsureStarted again.
+ void Stop();
+
+ // Remove cached data when GCM service is stopped.
+ void RemoveCachedData();
+
+ // Checks out of GCM and erases any cached and persisted data.
+ void CheckOut();
+
+ // Should be called when an app with |app_id| is trying to un/register.
+ // Checks whether another un/registration is in progress.
+ bool IsAsyncOperationPending(const std::string& app_id) const;
+
+ void DoRegister(const std::string& app_id,
+ const std::vector<std::string>& sender_ids);
+ void DoUnregister(const std::string& app_id);
+ void DoSend(const std::string& app_id,
+ const std::string& receiver_id,
+ const GCMClient::OutgoingMessage& message);
+
+ // Callbacks posted from IO thread to UI thread.
+ void RegisterFinished(const std::string& app_id,
+ const std::string& registration_id,
+ GCMClient::Result result);
+ void UnregisterFinished(const std::string& app_id, GCMClient::Result result);
+ void SendFinished(const std::string& app_id,
+ const std::string& message_id,
+ GCMClient::Result result);
+ void MessageReceived(const std::string& app_id,
+ GCMClient::IncomingMessage message);
+ void MessagesDeleted(const std::string& app_id);
+ void MessageSendError(const std::string& app_id,
+ const GCMClient::SendErrorDetails& send_error_details);
+ void GCMClientReady();
+
+ // Returns the handler for the given app.
+ GCMAppHandler* GetAppHandler(const std::string& app_id);
+
+ void GetGCMStatisticsFinished(GCMClient::GCMStatistics stats);
+
+ // Flag to indicate if GCM is enabled.
+ bool gcm_enabled_;
+
+ // Flag to indicate if GCMClient is ready.
+ bool gcm_client_ready_;
+
+ // The account ID that this service is responsible for. Empty when the service
+ // is not running.
+ std::string account_id_;
+
+ scoped_ptr<IdentityProvider> identity_provider_;
+ scoped_refptr<base::SequencedTaskRunner> ui_thread_;
+ scoped_refptr<base::SequencedTaskRunner> io_thread_;
+
+ scoped_ptr<DelayedTaskController> delayed_task_controller_;
+
+ // For all the work occurring on the IO thread. Must be destroyed on the IO
+ // thread.
+ scoped_ptr<IOWorker> io_worker_;
+
+ // App handler map (from app_id to handler pointer).
+ // The handler is not owned.
+ GCMAppHandlerMap app_handlers_;
+
+ // The default handler when no app handler can be found in the map.
+ DefaultGCMAppHandler default_app_handler_;
+
+ // Callback map (from app_id to callback) for Register.
+ std::map<std::string, RegisterCallback> register_callbacks_;
+
+ // Callback map (from app_id to callback) for Unregister.
+ std::map<std::string, UnregisterCallback> unregister_callbacks_;
+
+ // Callback map (from <app_id, message_id> to callback) for Send.
+ std::map<std::pair<std::string, std::string>, SendCallback> send_callbacks_;
+
+ // Callback for GetGCMStatistics.
+ GetGCMStatisticsCallback request_gcm_statistics_callback_;
+
+ // Used to pass a weak pointer to the IO worker.
+ base::WeakPtrFactory<GCMDriver> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(GCMDriver);
+};
+
+} // namespace gcm
+
+#endif // COMPONENTS_GCM_DRIVER_GCM_DRIVER_H_
diff --git a/components/gcm_driver/gcm_driver_unittest.cc b/components/gcm_driver/gcm_driver_unittest.cc
new file mode 100644
index 0000000..52f6abb
--- /dev/null
+++ b/components/gcm_driver/gcm_driver_unittest.cc
@@ -0,0 +1,900 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/gcm_driver/gcm_driver.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/run_loop.h"
+#include "base/strings/string_util.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread.h"
+#include "components/gcm_driver/fake_gcm_app_handler.h"
+#include "components/gcm_driver/fake_gcm_client.h"
+#include "components/gcm_driver/fake_gcm_client_factory.h"
+#include "components/gcm_driver/gcm_app_handler.h"
+#include "components/gcm_driver/gcm_client_factory.h"
+#include "google_apis/gaia/fake_identity_provider.h"
+#include "google_apis/gaia/fake_oauth2_token_service.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gcm {
+
+namespace {
+
+const char kTestAccountID1[] = "user1@example.com";
+const char kTestAccountID2[] = "user2@example.com";
+const char kTestAppID1[] = "TestApp1";
+const char kTestAppID2[] = "TestApp2";
+const char kUserID1[] = "user1";
+
+void PumpCurrentLoop() {
+ base::MessageLoop::ScopedNestableTaskAllower
+ nestable_task_allower(base::MessageLoop::current());
+ base::RunLoop().RunUntilIdle();
+}
+
+void PumpUILoop() {
+ PumpCurrentLoop();
+}
+
+std::vector<std::string> ToSenderList(const std::string& sender_ids) {
+ std::vector<std::string> senders;
+ Tokenize(sender_ids, ",", &senders);
+ return senders;
+}
+
+} // namespace
+
+class GCMDriverTest : public testing::Test {
+ public:
+ enum WaitToFinish {
+ DO_NOT_WAIT,
+ WAIT
+ };
+
+ GCMDriverTest();
+ virtual ~GCMDriverTest();
+
+ // testing::Test:
+ virtual void SetUp() OVERRIDE;
+ virtual void TearDown() OVERRIDE;
+
+ GCMDriver* driver() { return driver_.get(); }
+ FakeGCMAppHandler* gcm_app_handler() { return gcm_app_handler_.get(); }
+ const std::string& registration_id() const { return registration_id_; }
+ GCMClient::Result registration_result() const { return registration_result_; }
+ const std::string& send_message_id() const { return send_message_id_; }
+ GCMClient::Result send_result() const { return send_result_; }
+ GCMClient::Result unregistration_result() const {
+ return unregistration_result_;
+ }
+
+ void PumpIOLoop();
+
+ void ClearResults();
+
+ bool HasAppHandlers() const;
+ FakeGCMClient* GetGCMClient();
+
+ void CreateDriver(FakeGCMClient::StartMode gcm_client_start_mode);
+ void AddAppHandlers();
+ void RemoveAppHandlers();
+
+ void SignIn(const std::string& account_id);
+ void SignOut();
+
+ void Register(const std::string& app_id,
+ const std::vector<std::string>& sender_ids,
+ WaitToFinish wait_to_finish);
+ void Send(const std::string& app_id,
+ const std::string& receiver_id,
+ const GCMClient::OutgoingMessage& message,
+ WaitToFinish wait_to_finish);
+ void Unregister(const std::string& app_id, WaitToFinish wait_to_finish);
+
+ void WaitForAsyncOperation();
+
+ private:
+ void RegisterCompleted(const std::string& registration_id,
+ GCMClient::Result result);
+ void SendCompleted(const std::string& message_id, GCMClient::Result result);
+ void UnregisterCompleted(GCMClient::Result result);
+
+ base::ScopedTempDir temp_dir_;
+ FakeOAuth2TokenService token_service_;
+ scoped_ptr<FakeIdentityProvider> identity_provider_owner_;
+ FakeIdentityProvider* identity_provider_;
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ base::MessageLoopForUI message_loop_;
+ base::Thread io_thread_;
+ scoped_ptr<GCMDriver> driver_;
+ scoped_ptr<FakeGCMAppHandler> gcm_app_handler_;
+
+ base::Closure async_operation_completed_callback_;
+
+ std::string registration_id_;
+ GCMClient::Result registration_result_;
+ std::string send_message_id_;
+ GCMClient::Result send_result_;
+ GCMClient::Result unregistration_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(GCMDriverTest);
+};
+
+GCMDriverTest::GCMDriverTest()
+ : identity_provider_(NULL),
+ task_runner_(new base::TestSimpleTaskRunner()),
+ io_thread_("IOThread"),
+ registration_result_(GCMClient::UNKNOWN_ERROR),
+ send_result_(GCMClient::UNKNOWN_ERROR),
+ unregistration_result_(GCMClient::UNKNOWN_ERROR) {
+ identity_provider_owner_.reset(new FakeIdentityProvider(&token_service_));
+ identity_provider_ = identity_provider_owner_.get();
+}
+
+GCMDriverTest::~GCMDriverTest() {
+}
+
+void GCMDriverTest::SetUp() {
+ io_thread_.Start();
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+}
+
+void GCMDriverTest::TearDown() {
+ if (!driver_)
+ return;
+
+ driver_->Shutdown();
+ driver_.reset();
+ PumpIOLoop();
+
+ io_thread_.Stop();
+}
+
+void GCMDriverTest::PumpIOLoop() {
+ base::RunLoop run_loop;
+ io_thread_.message_loop_proxy()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&PumpCurrentLoop),
+ run_loop.QuitClosure());
+ run_loop.Run();
+}
+
+void GCMDriverTest::ClearResults() {
+ registration_id_.clear();
+ registration_result_ = GCMClient::UNKNOWN_ERROR;
+
+ send_message_id_.clear();
+ send_result_ = GCMClient::UNKNOWN_ERROR;
+
+ unregistration_result_ = GCMClient::UNKNOWN_ERROR;
+}
+
+bool GCMDriverTest::HasAppHandlers() const {
+ return !driver_->app_handlers().empty();
+}
+
+FakeGCMClient* GCMDriverTest::GetGCMClient() {
+ return static_cast<FakeGCMClient*>(driver_->GetGCMClientForTesting());
+}
+
+void GCMDriverTest::CreateDriver(
+ FakeGCMClient::StartMode gcm_client_start_mode) {
+ scoped_refptr<net::URLRequestContextGetter> request_context =
+ new net::TestURLRequestContextGetter(io_thread_.message_loop_proxy());
+ driver_.reset(new GCMDriver(
+ scoped_ptr<GCMClientFactory>(new FakeGCMClientFactory(
+ gcm_client_start_mode,
+ base::MessageLoopProxy::current(),
+ io_thread_.message_loop_proxy())).Pass(),
+ identity_provider_owner_.PassAs<IdentityProvider>(),
+ GCMClient::ChromeBuildInfo(),
+ temp_dir_.path(),
+ request_context,
+ base::MessageLoopProxy::current(),
+ io_thread_.message_loop_proxy(),
+ task_runner_));
+
+ gcm_app_handler_.reset(new FakeGCMAppHandler);
+}
+
+void GCMDriverTest::AddAppHandlers() {
+ driver_->AddAppHandler(kTestAppID1, gcm_app_handler_.get());
+ driver_->AddAppHandler(kTestAppID2, gcm_app_handler_.get());
+}
+
+void GCMDriverTest::RemoveAppHandlers() {
+ driver_->RemoveAppHandler(kTestAppID1);
+ driver_->RemoveAppHandler(kTestAppID2);
+}
+
+void GCMDriverTest::SignIn(const std::string& account_id) {
+ token_service_.AddAccount(account_id);
+ identity_provider_->LogIn(account_id);
+ PumpIOLoop();
+ PumpUILoop();
+}
+
+void GCMDriverTest::SignOut() {
+ identity_provider_->LogOut();
+ PumpIOLoop();
+ PumpUILoop();
+}
+
+void GCMDriverTest::Register(const std::string& app_id,
+ const std::vector<std::string>& sender_ids,
+ WaitToFinish wait_to_finish) {
+ base::RunLoop run_loop;
+ async_operation_completed_callback_ = run_loop.QuitClosure();
+ driver_->Register(app_id,
+ sender_ids,
+ base::Bind(&GCMDriverTest::RegisterCompleted,
+ base::Unretained(this)));
+ if (wait_to_finish == WAIT)
+ run_loop.Run();
+}
+
+void GCMDriverTest::Send(const std::string& app_id,
+ const std::string& receiver_id,
+ const GCMClient::OutgoingMessage& message,
+ WaitToFinish wait_to_finish) {
+ base::RunLoop run_loop;
+ async_operation_completed_callback_ = run_loop.QuitClosure();
+ driver_->Send(app_id,
+ receiver_id,
+ message,
+ base::Bind(&GCMDriverTest::SendCompleted,
+ base::Unretained(this)));
+ if (wait_to_finish == WAIT)
+ run_loop.Run();
+}
+
+void GCMDriverTest::Unregister(const std::string& app_id,
+ WaitToFinish wait_to_finish) {
+ base::RunLoop run_loop;
+ async_operation_completed_callback_ = run_loop.QuitClosure();
+ driver_->Unregister(app_id,
+ base::Bind(&GCMDriverTest::UnregisterCompleted,
+ base::Unretained(this)));
+ if (wait_to_finish == WAIT)
+ run_loop.Run();
+}
+
+void GCMDriverTest::WaitForAsyncOperation() {
+ base::RunLoop run_loop;
+ async_operation_completed_callback_ = run_loop.QuitClosure();
+ run_loop.Run();
+}
+
+void GCMDriverTest::RegisterCompleted(const std::string& registration_id,
+ GCMClient::Result result) {
+ registration_id_ = registration_id;
+ registration_result_ = result;
+ if (!async_operation_completed_callback_.is_null())
+ async_operation_completed_callback_.Run();
+}
+
+void GCMDriverTest::SendCompleted(const std::string& message_id,
+ GCMClient::Result result) {
+ send_message_id_ = message_id;
+ send_result_ = result;
+ if (!async_operation_completed_callback_.is_null())
+ async_operation_completed_callback_.Run();
+}
+
+void GCMDriverTest::UnregisterCompleted(GCMClient::Result result) {
+ unregistration_result_ = result;
+ if (!async_operation_completed_callback_.is_null())
+ async_operation_completed_callback_.Run();
+}
+
+TEST_F(GCMDriverTest, CreateGCMDriverBeforeSignIn) {
+ // Create GCMDriver first. GCM is not started.
+ CreateDriver(FakeGCMClient::NO_DELAY_START);
+ EXPECT_FALSE(driver()->IsStarted());
+
+ // Sign in. GCM is still not started.
+ SignIn(kTestAccountID1);
+ EXPECT_FALSE(driver()->IsStarted());
+
+ // GCM will be started only after both sign-in and app handler being
+ AddAppHandlers();
+ EXPECT_TRUE(driver()->IsStarted());
+}
+
+TEST_F(GCMDriverTest, CreateGCMDriverAfterSignIn) {
+ // Sign in. Nothings happens since GCMDriver is not created.
+ SignIn(kTestAccountID1);
+
+ // Create GCMDriver after sign-in. GCM is not started.
+ CreateDriver(FakeGCMClient::NO_DELAY_START);
+ EXPECT_FALSE(driver()->IsStarted());
+
+ // GCM will be started only after both sign-in and app handler being
+ AddAppHandlers();
+ EXPECT_TRUE(driver()->IsStarted());
+}
+
+TEST_F(GCMDriverTest, Shutdown) {
+ CreateDriver(FakeGCMClient::NO_DELAY_START);
+ EXPECT_FALSE(HasAppHandlers());
+
+ AddAppHandlers();
+ EXPECT_TRUE(HasAppHandlers());
+
+ driver()->Shutdown();
+ EXPECT_FALSE(HasAppHandlers());
+}
+
+TEST_F(GCMDriverTest, SignInAndSignOutOnGCMEnabled) {
+ // By default, GCM is enabled.
+ CreateDriver(FakeGCMClient::NO_DELAY_START);
+ AddAppHandlers();
+
+ // GCMClient should be started after sign-in.
+ SignIn(kTestAccountID1);
+ EXPECT_TRUE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::STARTED, GetGCMClient()->status());
+
+ // GCMClient should be checked out after sign-out.
+ SignOut();
+ EXPECT_FALSE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::CHECKED_OUT, GetGCMClient()->status());
+}
+
+TEST_F(GCMDriverTest, SignInAndSignOutOnGCMDisabled) {
+ // By default, GCM is enabled.
+ CreateDriver(FakeGCMClient::NO_DELAY_START);
+ AddAppHandlers();
+
+ // Disable GCM.
+ driver()->Disable();
+
+ // GCMClient should not be started after sign-in.
+ SignIn(kTestAccountID1);
+ EXPECT_FALSE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::UNINITIALIZED, GetGCMClient()->status());
+
+ // Check-out should still be performed after sign-out.
+ SignOut();
+ EXPECT_FALSE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::CHECKED_OUT, GetGCMClient()->status());
+}
+
+TEST_F(GCMDriverTest, SignOutAndThenSignIn) {
+ CreateDriver(FakeGCMClient::NO_DELAY_START);
+ AddAppHandlers();
+
+ // GCMClient should be started after sign-in.
+ SignIn(kTestAccountID1);
+ EXPECT_TRUE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::STARTED, GetGCMClient()->status());
+
+ // GCMClient should be checked out after sign-out.
+ SignOut();
+ EXPECT_FALSE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::CHECKED_OUT, GetGCMClient()->status());
+
+ // Sign-in with a different account.
+ SignIn(kTestAccountID2);
+
+ // GCMClient should be started again.
+ EXPECT_TRUE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::STARTED, GetGCMClient()->status());
+}
+
+TEST_F(GCMDriverTest, DisableAndReenableGCM) {
+ CreateDriver(FakeGCMClient::NO_DELAY_START);
+ AddAppHandlers();
+ SignIn(kTestAccountID1);
+
+ // GCMClient should be started.
+ EXPECT_TRUE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::STARTED, GetGCMClient()->status());
+
+ // Disables the GCM.
+ driver()->Disable();
+ PumpIOLoop();
+ PumpUILoop();
+
+ // GCMClient should be stopped.
+ EXPECT_FALSE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::STOPPED, GetGCMClient()->status());
+
+ // Enables the GCM.
+ driver()->Enable();
+ PumpIOLoop();
+ PumpUILoop();
+
+ // GCMClient should be started.
+ EXPECT_TRUE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::STARTED, GetGCMClient()->status());
+
+ // Disables the GCM.
+ driver()->Disable();
+ PumpIOLoop();
+ PumpUILoop();
+
+ // GCMClient should be stopped.
+ EXPECT_FALSE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::STOPPED, GetGCMClient()->status());
+
+ // Sign out.
+ SignOut();
+
+ // GCMClient should be checked out.
+ EXPECT_FALSE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::CHECKED_OUT, GetGCMClient()->status());
+}
+
+TEST_F(GCMDriverTest, StartOrStopGCMOnDemand) {
+ CreateDriver(FakeGCMClient::NO_DELAY_START);
+ SignIn(kTestAccountID1);
+
+ // GCMClient is not started.
+ EXPECT_FALSE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::UNINITIALIZED, GetGCMClient()->status());
+
+ // GCMClient is started after an app handler has been added.
+ driver()->AddAppHandler(kTestAppID1, gcm_app_handler());
+ PumpIOLoop();
+ PumpUILoop();
+ EXPECT_TRUE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::STARTED, GetGCMClient()->status());
+
+ // Add another app handler.
+ driver()->AddAppHandler(kTestAppID2, gcm_app_handler());
+ PumpIOLoop();
+ PumpUILoop();
+ EXPECT_TRUE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::STARTED, GetGCMClient()->status());
+
+ // GCMClient remains active after one app handler is gone.
+ driver()->RemoveAppHandler(kTestAppID1);
+ PumpIOLoop();
+ PumpUILoop();
+ EXPECT_TRUE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::STARTED, GetGCMClient()->status());
+
+ // GCMClient should be stopped after the last app handler is gone.
+ driver()->RemoveAppHandler(kTestAppID2);
+ PumpIOLoop();
+ PumpUILoop();
+ EXPECT_FALSE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::STOPPED, GetGCMClient()->status());
+
+ // GCMClient is restarted after an app handler has been added.
+ driver()->AddAppHandler(kTestAppID2, gcm_app_handler());
+ PumpIOLoop();
+ PumpUILoop();
+ EXPECT_TRUE(driver()->IsGCMClientReady());
+ EXPECT_EQ(FakeGCMClient::STARTED, GetGCMClient()->status());
+}
+
+TEST_F(GCMDriverTest, RegisterFailed) {
+ std::vector<std::string> sender_ids;
+ sender_ids.push_back("sender1");
+
+ CreateDriver(FakeGCMClient::NO_DELAY_START);
+
+ // Registration fails when GCM is disabled.
+ driver()->Disable();
+ Register(kTestAppID1, sender_ids, GCMDriverTest::WAIT);
+ EXPECT_TRUE(registration_id().empty());
+ EXPECT_EQ(GCMClient::GCM_DISABLED, registration_result());
+
+ ClearResults();
+
+ // Registration fails when the sign-in does not occur.
+ driver()->Enable();
+ AddAppHandlers();
+ Register(kTestAppID1, sender_ids, GCMDriverTest::WAIT);
+ EXPECT_TRUE(registration_id().empty());
+ EXPECT_EQ(GCMClient::NOT_SIGNED_IN, registration_result());
+
+ ClearResults();
+
+ // Registration fails when the no app handler is added.
+ RemoveAppHandlers();
+ SignIn(kTestAccountID1);
+ Register(kTestAppID1, sender_ids, GCMDriverTest::WAIT);
+ EXPECT_TRUE(registration_id().empty());
+ EXPECT_EQ(GCMClient::UNKNOWN_ERROR, registration_result());
+}
+
+TEST_F(GCMDriverTest, UnregisterFailed) {
+ CreateDriver(FakeGCMClient::NO_DELAY_START);
+
+ // Unregistration fails when GCM is disabled.
+ driver()->Disable();
+ Unregister(kTestAppID1, GCMDriverTest::WAIT);
+ EXPECT_EQ(GCMClient::GCM_DISABLED, unregistration_result());
+
+ ClearResults();
+
+ // Unregistration fails when the sign-in does not occur.
+ driver()->Enable();
+ AddAppHandlers();
+ Unregister(kTestAppID1, GCMDriverTest::WAIT);
+ EXPECT_EQ(GCMClient::NOT_SIGNED_IN, unregistration_result());
+
+ ClearResults();
+
+ // Unregistration fails when the no app handler is added.
+ RemoveAppHandlers();
+ SignIn(kTestAccountID1);
+ Unregister(kTestAppID1, GCMDriverTest::WAIT);
+ EXPECT_EQ(GCMClient::UNKNOWN_ERROR, unregistration_result());
+}
+
+TEST_F(GCMDriverTest, SendFailed) {
+ GCMClient::OutgoingMessage message;
+ message.id = "1";
+ message.data["key1"] = "value1";
+
+ CreateDriver(FakeGCMClient::NO_DELAY_START);
+
+ // Sending fails when GCM is disabled.
+ driver()->Disable();
+ Send(kTestAppID1, kUserID1, message, GCMDriverTest::WAIT);
+ EXPECT_TRUE(send_message_id().empty());
+ EXPECT_EQ(GCMClient::GCM_DISABLED, send_result());
+
+ ClearResults();
+
+ // Sending fails when the sign-in does not occur.
+ driver()->Enable();
+ AddAppHandlers();
+ Send(kTestAppID1, kUserID1, message, GCMDriverTest::WAIT);
+ EXPECT_TRUE(send_message_id().empty());
+ EXPECT_EQ(GCMClient::NOT_SIGNED_IN, send_result());
+
+ ClearResults();
+
+ // Sending fails when the no app handler is added.
+ RemoveAppHandlers();
+ SignIn(kTestAccountID1);
+ Send(kTestAppID1, kUserID1, message, GCMDriverTest::WAIT);
+ EXPECT_TRUE(send_message_id().empty());
+ EXPECT_EQ(GCMClient::UNKNOWN_ERROR, send_result());
+}
+
+TEST_F(GCMDriverTest, GCMClientNotReadyBeforeRegistration) {
+ // Make GCMClient not ready initially.
+ CreateDriver(FakeGCMClient::DELAY_START);
+ SignIn(kTestAccountID1);
+ AddAppHandlers();
+
+ // The registration is on hold until GCMClient is ready.
+ std::vector<std::string> sender_ids;
+ sender_ids.push_back("sender1");
+ Register(kTestAppID1,
+ sender_ids,
+ GCMDriverTest::DO_NOT_WAIT);
+ PumpIOLoop();
+ PumpUILoop();
+ EXPECT_TRUE(registration_id().empty());
+ EXPECT_EQ(GCMClient::UNKNOWN_ERROR, registration_result());
+
+ // Register operation will be invoked after GCMClient becomes ready.
+ GetGCMClient()->PerformDelayedLoading();
+ WaitForAsyncOperation();
+ EXPECT_FALSE(registration_id().empty());
+ EXPECT_EQ(GCMClient::SUCCESS, registration_result());
+}
+
+TEST_F(GCMDriverTest, GCMClientNotReadyBeforeSending) {
+ // Make GCMClient not ready initially.
+ CreateDriver(FakeGCMClient::DELAY_START);
+ SignIn(kTestAccountID1);
+ AddAppHandlers();
+
+ // The sending is on hold until GCMClient is ready.
+ GCMClient::OutgoingMessage message;
+ message.id = "1";
+ message.data["key1"] = "value1";
+ message.data["key2"] = "value2";
+ Send(kTestAppID1, kUserID1, message, GCMDriverTest::DO_NOT_WAIT);
+ PumpIOLoop();
+ PumpUILoop();
+
+ EXPECT_TRUE(send_message_id().empty());
+ EXPECT_EQ(GCMClient::UNKNOWN_ERROR, send_result());
+
+ // Send operation will be invoked after GCMClient becomes ready.
+ GetGCMClient()->PerformDelayedLoading();
+ WaitForAsyncOperation();
+ EXPECT_EQ(message.id, send_message_id());
+ EXPECT_EQ(GCMClient::SUCCESS, send_result());
+}
+
+// Tests a single instance of GCMDriver.
+class GCMDriverFunctionalTest : public GCMDriverTest {
+ public:
+ GCMDriverFunctionalTest();
+ virtual ~GCMDriverFunctionalTest();
+
+ // GCMDriverTest:
+ virtual void SetUp() OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(GCMDriverFunctionalTest);
+};
+
+GCMDriverFunctionalTest::GCMDriverFunctionalTest() {
+}
+
+GCMDriverFunctionalTest::~GCMDriverFunctionalTest() {
+}
+
+void GCMDriverFunctionalTest::SetUp() {
+ GCMDriverTest::SetUp();
+
+ CreateDriver(FakeGCMClient::NO_DELAY_START);
+ AddAppHandlers();
+ SignIn(kTestAccountID1);
+}
+
+TEST_F(GCMDriverFunctionalTest, Register) {
+ std::vector<std::string> sender_ids;
+ sender_ids.push_back("sender1");
+ Register(kTestAppID1, sender_ids, GCMDriverTest::WAIT);
+ const std::string expected_registration_id =
+ FakeGCMClient::GetRegistrationIdFromSenderIds(sender_ids);
+
+ EXPECT_EQ(expected_registration_id, registration_id());
+ EXPECT_EQ(GCMClient::SUCCESS, registration_result());
+}
+
+TEST_F(GCMDriverFunctionalTest, RegisterError) {
+ std::vector<std::string> sender_ids;
+ sender_ids.push_back("sender1@error");
+ Register(kTestAppID1, sender_ids, GCMDriverTest::WAIT);
+
+ EXPECT_TRUE(registration_id().empty());
+ EXPECT_NE(GCMClient::SUCCESS, registration_result());
+}
+
+TEST_F(GCMDriverFunctionalTest, RegisterAgainWithSameSenderIDs) {
+ std::vector<std::string> sender_ids;
+ sender_ids.push_back("sender1");
+ sender_ids.push_back("sender2");
+ Register(kTestAppID1, sender_ids, GCMDriverTest::WAIT);
+ const std::string expected_registration_id =
+ FakeGCMClient::GetRegistrationIdFromSenderIds(sender_ids);
+
+ EXPECT_EQ(expected_registration_id, registration_id());
+ EXPECT_EQ(GCMClient::SUCCESS, registration_result());
+
+ // Clears the results the would be set by the Register callback in preparation
+ // to call register 2nd time.
+ ClearResults();
+
+ // Calling register 2nd time with the same set of sender IDs but different
+ // ordering will get back the same registration ID.
+ std::vector<std::string> another_sender_ids;
+ another_sender_ids.push_back("sender2");
+ another_sender_ids.push_back("sender1");
+ Register(kTestAppID1, another_sender_ids, GCMDriverTest::WAIT);
+
+ EXPECT_EQ(expected_registration_id, registration_id());
+ EXPECT_EQ(GCMClient::SUCCESS, registration_result());
+}
+
+TEST_F(GCMDriverFunctionalTest, RegisterAgainWithDifferentSenderIDs) {
+ std::vector<std::string> sender_ids;
+ sender_ids.push_back("sender1");
+ Register(kTestAppID1, sender_ids, GCMDriverTest::WAIT);
+ const std::string expected_registration_id =
+ FakeGCMClient::GetRegistrationIdFromSenderIds(sender_ids);
+
+ EXPECT_EQ(expected_registration_id, registration_id());
+ EXPECT_EQ(GCMClient::SUCCESS, registration_result());
+
+ // Make sender IDs different.
+ sender_ids.push_back("sender2");
+ const std::string expected_registration_id2 =
+ FakeGCMClient::GetRegistrationIdFromSenderIds(sender_ids);
+
+ // Calling register 2nd time with the different sender IDs will get back a new
+ // registration ID.
+ Register(kTestAppID1, sender_ids, GCMDriverTest::WAIT);
+ EXPECT_EQ(expected_registration_id2, registration_id());
+ EXPECT_EQ(GCMClient::SUCCESS, registration_result());
+}
+
+TEST_F(GCMDriverFunctionalTest, RegisterAfterSignOut) {
+ // This will trigger check-out.
+ SignOut();
+
+ std::vector<std::string> sender_ids;
+ sender_ids.push_back("sender1");
+ Register(kTestAppID1, sender_ids, GCMDriverTest::WAIT);
+
+ EXPECT_TRUE(registration_id().empty());
+ EXPECT_EQ(GCMClient::NOT_SIGNED_IN, registration_result());
+}
+
+TEST_F(GCMDriverFunctionalTest, UnregisterExplicitly) {
+ std::vector<std::string> sender_ids;
+ sender_ids.push_back("sender1");
+ Register(kTestAppID1, sender_ids, GCMDriverTest::WAIT);
+
+ EXPECT_FALSE(registration_id().empty());
+ EXPECT_EQ(GCMClient::SUCCESS, registration_result());
+
+ Unregister(kTestAppID1, GCMDriverTest::WAIT);
+
+ EXPECT_EQ(GCMClient::SUCCESS, unregistration_result());
+}
+
+TEST_F(GCMDriverFunctionalTest, UnregisterWhenAsyncOperationPending) {
+ std::vector<std::string> sender_ids;
+ sender_ids.push_back("sender1");
+ // First start registration without waiting for it to complete.
+ Register(kTestAppID1,
+ sender_ids,
+ GCMDriverTest::DO_NOT_WAIT);
+
+ // Test that unregistration fails with async operation pending when there is a
+ // registration already in progress.
+ Unregister(kTestAppID1, GCMDriverTest::WAIT);
+ EXPECT_EQ(GCMClient::ASYNC_OPERATION_PENDING,
+ unregistration_result());
+
+ // Complete the unregistration.
+ WaitForAsyncOperation();
+ EXPECT_EQ(GCMClient::SUCCESS, registration_result());
+
+ // Start unregistration without waiting for it to complete. This time no async
+ // operation is pending.
+ Unregister(kTestAppID1, GCMDriverTest::DO_NOT_WAIT);
+
+ // Test that unregistration fails with async operation pending when there is
+ // an unregistration already in progress.
+ Unregister(kTestAppID1, GCMDriverTest::WAIT);
+ EXPECT_EQ(GCMClient::ASYNC_OPERATION_PENDING,
+ unregistration_result());
+ ClearResults();
+
+ // Complete unregistration.
+ WaitForAsyncOperation();
+ EXPECT_EQ(GCMClient::SUCCESS, unregistration_result());
+}
+
+TEST_F(GCMDriverFunctionalTest, RegisterWhenAsyncOperationPending) {
+ std::vector<std::string> sender_ids;
+ sender_ids.push_back("sender1");
+ // First start registration without waiting for it to complete.
+ Register(kTestAppID1,
+ sender_ids,
+ GCMDriverTest::DO_NOT_WAIT);
+
+ // Test that registration fails with async operation pending when there is a
+ // registration already in progress.
+ Register(kTestAppID1, sender_ids, GCMDriverTest::WAIT);
+ EXPECT_EQ(GCMClient::ASYNC_OPERATION_PENDING,
+ registration_result());
+ ClearResults();
+
+ // Complete the registration.
+ WaitForAsyncOperation();
+ EXPECT_EQ(GCMClient::SUCCESS, registration_result());
+
+ // Start unregistration without waiting for it to complete. This time no async
+ // operation is pending.
+ Unregister(kTestAppID1, GCMDriverTest::DO_NOT_WAIT);
+
+ // Test that registration fails with async operation pending when there is an
+ // unregistration already in progress.
+ Register(kTestAppID1, sender_ids, GCMDriverTest::WAIT);
+ EXPECT_EQ(GCMClient::ASYNC_OPERATION_PENDING,
+ registration_result());
+
+ // Complete the first unregistration expecting success.
+ WaitForAsyncOperation();
+ EXPECT_EQ(GCMClient::SUCCESS, unregistration_result());
+
+ // Test that it is ok to register again after unregistration.
+ Register(kTestAppID1, sender_ids, GCMDriverTest::WAIT);
+ EXPECT_EQ(GCMClient::SUCCESS, registration_result());
+}
+
+TEST_F(GCMDriverFunctionalTest, Send) {
+ GCMClient::OutgoingMessage message;
+ message.id = "1";
+ message.data["key1"] = "value1";
+ message.data["key2"] = "value2";
+ Send(kTestAppID1, kUserID1, message, GCMDriverTest::WAIT);
+
+ EXPECT_EQ(message.id, send_message_id());
+ EXPECT_EQ(GCMClient::SUCCESS, send_result());
+}
+
+TEST_F(GCMDriverFunctionalTest, SendAfterSignOut) {
+ // This will trigger check-out.
+ SignOut();
+
+ GCMClient::OutgoingMessage message;
+ message.id = "1";
+ message.data["key1"] = "value1";
+ message.data["key2"] = "value2";
+ Send(kTestAppID1, kUserID1, message, GCMDriverTest::WAIT);
+
+ EXPECT_TRUE(send_message_id().empty());
+ EXPECT_EQ(GCMClient::NOT_SIGNED_IN, send_result());
+}
+
+TEST_F(GCMDriverFunctionalTest, SendError) {
+ GCMClient::OutgoingMessage message;
+ // Embedding error in id will tell the mock to simulate the send error.
+ message.id = "1@error";
+ message.data["key1"] = "value1";
+ message.data["key2"] = "value2";
+ Send(kTestAppID1, kUserID1, message, GCMDriverTest::WAIT);
+
+ EXPECT_EQ(message.id, send_message_id());
+ EXPECT_EQ(GCMClient::SUCCESS, send_result());
+
+ // Wait for the send error.
+ gcm_app_handler()->WaitForNotification();
+ EXPECT_EQ(FakeGCMAppHandler::SEND_ERROR_EVENT,
+ gcm_app_handler()->received_event());
+ EXPECT_EQ(kTestAppID1, gcm_app_handler()->app_id());
+ EXPECT_EQ(message.id,
+ gcm_app_handler()->send_error_details().message_id);
+ EXPECT_NE(GCMClient::SUCCESS,
+ gcm_app_handler()->send_error_details().result);
+ EXPECT_EQ(message.data,
+ gcm_app_handler()->send_error_details().additional_data);
+}
+
+TEST_F(GCMDriverFunctionalTest, MessageReceived) {
+ Register(kTestAppID1, ToSenderList("sender"), GCMDriverTest::WAIT);
+ GCMClient::IncomingMessage message;
+ message.data["key1"] = "value1";
+ message.data["key2"] = "value2";
+ message.sender_id = "sender";
+ GetGCMClient()->ReceiveMessage(kTestAppID1, message);
+ gcm_app_handler()->WaitForNotification();
+ EXPECT_EQ(FakeGCMAppHandler::MESSAGE_EVENT,
+ gcm_app_handler()->received_event());
+ EXPECT_EQ(kTestAppID1, gcm_app_handler()->app_id());
+ EXPECT_EQ(message.data, gcm_app_handler()->message().data);
+ EXPECT_TRUE(gcm_app_handler()->message().collapse_key.empty());
+ EXPECT_EQ(message.sender_id, gcm_app_handler()->message().sender_id);
+}
+
+TEST_F(GCMDriverFunctionalTest, MessageWithCollapseKeyReceived) {
+ Register(kTestAppID1, ToSenderList("sender"), GCMDriverTest::WAIT);
+ GCMClient::IncomingMessage message;
+ message.data["key1"] = "value1";
+ message.collapse_key = "collapse_key_value";
+ message.sender_id = "sender";
+ GetGCMClient()->ReceiveMessage(kTestAppID1, message);
+ gcm_app_handler()->WaitForNotification();
+ EXPECT_EQ(FakeGCMAppHandler::MESSAGE_EVENT,
+ gcm_app_handler()->received_event());
+ EXPECT_EQ(kTestAppID1, gcm_app_handler()->app_id());
+ EXPECT_EQ(message.data, gcm_app_handler()->message().data);
+ EXPECT_EQ(message.collapse_key,
+ gcm_app_handler()->message().collapse_key);
+}
+
+TEST_F(GCMDriverFunctionalTest, MessagesDeleted) {
+ GetGCMClient()->DeleteMessages(kTestAppID1);
+ gcm_app_handler()->WaitForNotification();
+ EXPECT_EQ(FakeGCMAppHandler::MESSAGES_DELETED_EVENT,
+ gcm_app_handler()->received_event());
+ EXPECT_EQ(kTestAppID1, gcm_app_handler()->app_id());
+}
+
+} // namespace gcm