diff options
author | jianli@chromium.org <jianli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-28 03:18:49 +0000 |
---|---|---|
committer | jianli@chromium.org <jianli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-28 03:18:49 +0000 |
commit | 8d41e5a69e84b2d02e7289ef2f8877e20962400d (patch) | |
tree | 891f982a88639fc11125ec0dd3ff923834a74e14 /components/gcm_driver | |
parent | 07c770d1a21bce5af5eb0d79d6c02337ddedfe10 (diff) | |
download | chromium_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/DEPS | 2 | ||||
-rw-r--r-- | components/gcm_driver/fake_gcm_app_handler.cc | 62 | ||||
-rw-r--r-- | components/gcm_driver/fake_gcm_app_handler.h | 63 | ||||
-rw-r--r-- | components/gcm_driver/fake_gcm_client.cc | 221 | ||||
-rw-r--r-- | components/gcm_driver/fake_gcm_client.h | 102 | ||||
-rw-r--r-- | components/gcm_driver/fake_gcm_client_factory.cc | 30 | ||||
-rw-r--r-- | components/gcm_driver/fake_gcm_client_factory.h | 42 | ||||
-rw-r--r-- | components/gcm_driver/gcm_driver.cc | 821 | ||||
-rw-r--r-- | components/gcm_driver/gcm_driver.h | 233 | ||||
-rw-r--r-- | components/gcm_driver/gcm_driver_unittest.cc | 900 |
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 |