From 21b7765ae5142044dd95ce042ad93f9f2b963b17 Mon Sep 17 00:00:00 2001 From: "johnme@chromium.org" Date: Sat, 31 May 2014 01:21:09 +0000 Subject: This patch splits GCMDriver into an abstract interface and an implementation thereof for desktop platforms. It also provides a stub Android implementation, to be fleshed out in subsequent patches. BUG=350384 TEST=existing tests TBR=pavely@chromium.org for updates to invalidation tests Review URL: https://codereview.chromium.org/278493002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@273977 0039d316-1c4b-4281-b951-d872f2087c98 --- components/components_tests.gyp | 3 +- components/gcm_driver.gypi | 8 + components/gcm_driver/fake_gcm_driver.cc | 70 ++ components/gcm_driver/fake_gcm_driver.h | 52 ++ components/gcm_driver/gcm_driver.cc | 781 +----------------- components/gcm_driver/gcm_driver.h | 158 +--- components/gcm_driver/gcm_driver_android.cc | 63 +- components/gcm_driver/gcm_driver_android.h | 38 +- components/gcm_driver/gcm_driver_desktop.cc | 809 ++++++++++++++++++ components/gcm_driver/gcm_driver_desktop.h | 171 ++++ .../gcm_driver/gcm_driver_desktop_unittest.cc | 901 +++++++++++++++++++++ components/gcm_driver/gcm_driver_unittest.cc | 900 -------------------- 12 files changed, 2134 insertions(+), 1820 deletions(-) create mode 100644 components/gcm_driver/fake_gcm_driver.cc create mode 100644 components/gcm_driver/fake_gcm_driver.h create mode 100644 components/gcm_driver/gcm_driver_desktop.cc create mode 100644 components/gcm_driver/gcm_driver_desktop.h create mode 100644 components/gcm_driver/gcm_driver_desktop_unittest.cc delete mode 100644 components/gcm_driver/gcm_driver_unittest.cc (limited to 'components') diff --git a/components/components_tests.gyp b/components/components_tests.gyp index f5f530d..99d3307 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -97,7 +97,7 @@ 'domain_reliability/util_unittest.cc', 'enhanced_bookmarks/image_store_unittest.cc', 'feedback/feedback_uploader_unittest.cc', - 'gcm_driver/gcm_driver_unittest.cc', + 'gcm_driver/gcm_driver_desktop_unittest.cc', 'invalidation/invalidation_logger_unittest.cc', 'json_schema/json_schema_validator_unittest.cc', 'json_schema/json_schema_validator_unittest_base.cc', @@ -432,6 +432,7 @@ }], ['OS == "android"', { 'sources!': [ + 'gcm_driver/gcm_driver_desktop_unittest.cc', 'feedback/feedback_uploader_unittest.cc', 'signin/core/browser/mutable_profile_oauth2_token_service_unittest.cc', 'storage_monitor/media_storage_util_unittest.cc', diff --git a/components/gcm_driver.gypi b/components/gcm_driver.gypi index a2e5455..7b3bb4a 100644 --- a/components/gcm_driver.gypi +++ b/components/gcm_driver.gypi @@ -28,6 +28,8 @@ 'gcm_driver/gcm_driver.h', 'gcm_driver/gcm_driver_android.cc', 'gcm_driver/gcm_driver_android.h', + 'gcm_driver/gcm_driver_desktop.cc', + 'gcm_driver/gcm_driver_desktop.h', 'gcm_driver/system_encryptor.cc', 'gcm_driver/system_encryptor.h', ], @@ -36,6 +38,10 @@ 'dependencies': [ 'gcm_driver_jni_headers', ], + 'sources!': [ + 'gcm_driver/gcm_driver_desktop.cc', + 'gcm_driver/gcm_driver_desktop.h', + ], }], ], }, @@ -57,6 +63,8 @@ 'gcm_driver/fake_gcm_client.h', 'gcm_driver/fake_gcm_client_factory.cc', 'gcm_driver/fake_gcm_client_factory.h', + 'gcm_driver/fake_gcm_driver.cc', + 'gcm_driver/fake_gcm_driver.h', ], }, ], diff --git a/components/gcm_driver/fake_gcm_driver.cc b/components/gcm_driver/fake_gcm_driver.cc new file mode 100644 index 0000000..667b824 --- /dev/null +++ b/components/gcm_driver/fake_gcm_driver.cc @@ -0,0 +1,70 @@ +// 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_driver.h" + +namespace gcm { + +FakeGCMDriver::FakeGCMDriver() { +} + +FakeGCMDriver::~FakeGCMDriver() { +} + +void FakeGCMDriver::Shutdown() { +} + +void FakeGCMDriver::AddAppHandler( + const std::string& app_id, GCMAppHandler* handler) { +} + +void FakeGCMDriver::RemoveAppHandler(const std::string& app_id) { +} + +void FakeGCMDriver::Enable() { +} + +void FakeGCMDriver::Disable() { +} + +void FakeGCMDriver::Register(const std::string& app_id, + const std::vector& sender_ids, + const RegisterCallback& callback) { +} + +void FakeGCMDriver::Unregister(const std::string& app_id, + const UnregisterCallback& callback) { +} + +void FakeGCMDriver::Send(const std::string& app_id, + const std::string& receiver_id, + const GCMClient::OutgoingMessage& message, + const SendCallback& callback) { +} + +GCMClient* FakeGCMDriver::GetGCMClientForTesting() const { + return NULL; +} + +bool FakeGCMDriver::IsStarted() const { + return true; +} + +bool FakeGCMDriver::IsGCMClientReady() const { + return true; +} + +void FakeGCMDriver::GetGCMStatistics(const GetGCMStatisticsCallback& callback, + bool clear_logs) { +} + +void FakeGCMDriver::SetGCMRecording(const GetGCMStatisticsCallback& callback, + bool recording) { +} + +std::string FakeGCMDriver::SignedInUserName() const { + return std::string(); +} + +} // namespace gcm diff --git a/components/gcm_driver/fake_gcm_driver.h b/components/gcm_driver/fake_gcm_driver.h new file mode 100644 index 0000000..b08f9c3 --- /dev/null +++ b/components/gcm_driver/fake_gcm_driver.h @@ -0,0 +1,52 @@ +// 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_DRIVER_H_ +#define COMPONENTS_GCM_DRIVER_FAKE_GCM_DRIVER_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "components/gcm_driver/gcm_driver.h" + +namespace gcm { + +class FakeGCMDriver : public GCMDriver { + public: + FakeGCMDriver(); + virtual ~FakeGCMDriver(); + + // GCMDriver overrides: + virtual void Shutdown() OVERRIDE; + virtual void AddAppHandler(const std::string& app_id, + GCMAppHandler* handler) OVERRIDE; + virtual void RemoveAppHandler(const std::string& app_id) OVERRIDE; + + // GCMDriver implementation: + virtual void Enable() OVERRIDE; + virtual void Disable() OVERRIDE; + virtual void Register(const std::string& app_id, + const std::vector& sender_ids, + const RegisterCallback& callback) OVERRIDE; + virtual void Unregister(const std::string& app_id, + const UnregisterCallback& callback) OVERRIDE; + virtual void Send(const std::string& app_id, + const std::string& receiver_id, + const GCMClient::OutgoingMessage& message, + const SendCallback& callback) OVERRIDE; + virtual GCMClient* GetGCMClientForTesting() const OVERRIDE; + virtual bool IsStarted() const OVERRIDE; + virtual bool IsGCMClientReady() const OVERRIDE; + virtual void GetGCMStatistics(const GetGCMStatisticsCallback& callback, + bool clear_logs) OVERRIDE; + virtual void SetGCMRecording(const GetGCMStatisticsCallback& callback, + bool recording) OVERRIDE; + virtual std::string SignedInUserName() const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(FakeGCMDriver); +}; + +} // namespace gcm + +#endif // COMPONENTS_GCM_DRIVER_FAKE_GCM_DRIVER_H_ diff --git a/components/gcm_driver/gcm_driver.cc b/components/gcm_driver/gcm_driver.cc index e41972e..866e6ca 100644 --- a/components/gcm_driver/gcm_driver.cc +++ b/components/gcm_driver/gcm_driver.cc @@ -4,818 +4,41 @@ #include "components/gcm_driver/gcm_driver.h" -#include -#include - -#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 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& ui_thread, - const scoped_refptr& 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 gcm_client_factory, - const GCMClient::ChromeBuildInfo& chrome_build_info, - const base::FilePath& store_path, - const std::vector& account_ids, - const scoped_refptr& request_context, - const scoped_refptr blocking_task_runner); - void Start(const base::WeakPtr& service); - void Stop(); - void CheckOut(); - void Register(const std::string& app_id, - const std::vector& 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 ui_thread_; - scoped_refptr io_thread_; - - base::WeakPtr service_; - - scoped_ptr gcm_client_; - - DISALLOW_COPY_AND_ASSIGN(IOWorker); -}; - -GCMDriver::IOWorker::IOWorker( - const scoped_refptr& ui_thread, - const scoped_refptr& 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 gcm_client_factory, - const GCMClient::ChromeBuildInfo& chrome_build_info, - const base::FilePath& store_path, - const std::vector& account_ids, - const scoped_refptr& request_context, - const scoped_refptr 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(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& 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& 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 gcm_client_factory, - scoped_ptr identity_provider, - const GCMClient::ChromeBuildInfo& chrome_build_info, - const base::FilePath& store_path, - const scoped_refptr& request_context, - const scoped_refptr& ui_thread, - const scoped_refptr& io_thread, - const scoped_refptr& 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 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() { } 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& 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& sender_ids) { - DCHECK(ui_thread_->RunsTasksOnCurrentThread()); - std::map::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 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 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::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::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, SendCallback>::iterator - callback_iter = send_callbacks_.find( - std::pair(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::const_iterator iter = - app_handlers_.find(app_id); + GCMAppHandlerMap::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 index 897dbfa..367a6503 100644 --- a/components/gcm_driver/gcm_driver.h +++ b/components/gcm_driver/gcm_driver.h @@ -9,36 +9,18 @@ #include #include -#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 "base/macros.h" +#include "base/threading/thread_checker.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 { +// Bridge between GCM users in Chrome and the platform-specific implementation. +class GCMDriver { public: typedef std::map GCMAppHandlerMap; typedef base::Callback GetGCMStatisticsCallback; - GCMDriver( - scoped_ptr gcm_client_factory, - scoped_ptr identity_provider, - const GCMClient::ChromeBuildInfo& chrome_build_info, - const base::FilePath& store_path, - const scoped_refptr& request_context, - const scoped_refptr& ui_thread, - const scoped_refptr& io_thread, - const scoped_refptr& blocking_task_runner); + GCMDriver(); 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(); @@ -74,6 +44,10 @@ class GCMDriver : public IdentityProvider::Observer { // Remove the handler for a given app. virtual void RemoveAppHandler(const std::string& app_id); + // Enables/disables GCM service. + virtual void Enable() = 0; + virtual void Disable() = 0; + // Registers |sender_id| for an app. A registration ID will be returned by // the GCM server. // |app_id|: application ID. @@ -83,13 +57,13 @@ class GCMDriver : public IdentityProvider::Observer { // |callback|: to be called once the asynchronous operation is done. virtual void Register(const std::string& app_id, const std::vector& sender_ids, - const RegisterCallback& callback); + const RegisterCallback& callback) = 0; // 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); + const UnregisterCallback& callback) = 0; // Sends a message to a given receiver. // |app_id|: application ID. @@ -99,110 +73,37 @@ class GCMDriver : public IdentityProvider::Observer { virtual void Send(const std::string& app_id, const std::string& receiver_id, const GCMClient::OutgoingMessage& message, - const SendCallback& callback); + const SendCallback& callback) = 0; - // For testing purpose. - GCMClient* GetGCMClientForTesting() const; + // For testing purpose. Always NULL on Android. + virtual GCMClient* GetGCMClientForTesting() const = 0; // Returns true if the service was started. - bool IsStarted() const; + virtual bool IsStarted() const = 0; // Returns true if the gcm client is ready. - bool IsGCMClientReady() const; + virtual bool IsGCMClientReady() const = 0; // 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); + virtual void GetGCMStatistics(const GetGCMStatisticsCallback& callback, + bool clear_logs) = 0; // Enables/disables GCM activity recording, and then returns the stats. - void SetGCMRecording(const GetGCMStatisticsCallback& callback, - bool recording); + virtual void SetGCMRecording(const GetGCMStatisticsCallback& callback, + bool recording) = 0; // 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; + virtual std::string SignedInUserName() const = 0; 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& 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 identity_provider_; - scoped_refptr ui_thread_; - scoped_refptr io_thread_; - - scoped_ptr delayed_task_controller_; - - // For all the work occurring on the IO thread. Must be destroyed on the IO - // thread. - scoped_ptr io_worker_; - + private: // App handler map (from app_id to handler pointer). // The handler is not owned. GCMAppHandlerMap app_handlers_; @@ -210,21 +111,6 @@ class GCMDriver : public IdentityProvider::Observer { // 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 register_callbacks_; - - // Callback map (from app_id to callback) for Unregister. - std::map unregister_callbacks_; - - // Callback map (from to callback) for Send. - std::map, SendCallback> send_callbacks_; - - // Callback for GetGCMStatistics. - GetGCMStatisticsCallback request_gcm_statistics_callback_; - - // Used to pass a weak pointer to the IO worker. - base::WeakPtrFactory weak_ptr_factory_; - DISALLOW_COPY_AND_ASSIGN(GCMDriver); }; diff --git a/components/gcm_driver/gcm_driver_android.cc b/components/gcm_driver/gcm_driver_android.cc index e7ae955..76f187e 100644 --- a/components/gcm_driver/gcm_driver_android.cc +++ b/components/gcm_driver/gcm_driver_android.cc @@ -5,6 +5,7 @@ #include "components/gcm_driver/gcm_driver_android.h" #include "base/compiler_specific.h" +#include "base/logging.h" namespace gcm { static void Java_GCMDriver_doNothing(JNIEnv* env) ALLOW_UNUSED; @@ -15,9 +16,69 @@ static void Java_GCMDriver_doNothing(JNIEnv* env) ALLOW_UNUSED; namespace gcm { +GCMDriverAndroid::GCMDriverAndroid() { +} + +GCMDriverAndroid::~GCMDriverAndroid() { +} + +void GCMDriverAndroid::Enable() { +} + +void GCMDriverAndroid::Disable() { +} + +void GCMDriverAndroid::Register(const std::string& app_id, + const std::vector& sender_ids, + const RegisterCallback& callback) { + // TODO(johnme): Hook up to Android GCM API via JNI. + NOTIMPLEMENTED(); +} + +void GCMDriverAndroid::Unregister(const std::string& app_id, + const UnregisterCallback& callback) { + // TODO(johnme): Hook up to Android GCM API via JNI. + NOTIMPLEMENTED(); +} + +void GCMDriverAndroid::Send(const std::string& app_id, + const std::string& receiver_id, + const GCMClient::OutgoingMessage& message, + const SendCallback& callback) { + NOTIMPLEMENTED(); +} + +GCMClient* GCMDriverAndroid::GetGCMClientForTesting() const { + NOTIMPLEMENTED(); + return NULL; +} + +bool GCMDriverAndroid::IsStarted() const { + return true; +} + +bool GCMDriverAndroid::IsGCMClientReady() const { + return true; +} + +void GCMDriverAndroid::GetGCMStatistics( + const GetGCMStatisticsCallback& callback, + bool clear_logs) { + NOTIMPLEMENTED(); +} + +void GCMDriverAndroid::SetGCMRecording(const GetGCMStatisticsCallback& callback, + bool recording) { + NOTIMPLEMENTED(); +} + +std::string GCMDriverAndroid::SignedInUserName() const { + return std::string(); +} + // static bool GCMDriverAndroid::RegisterBindings(JNIEnv* env) { return RegisterNativesImpl(env); } -} // namespace gcm \ No newline at end of file +} // namespace gcm diff --git a/components/gcm_driver/gcm_driver_android.h b/components/gcm_driver/gcm_driver_android.h index 653a4f2..0d0e876 100644 --- a/components/gcm_driver/gcm_driver_android.h +++ b/components/gcm_driver/gcm_driver_android.h @@ -7,14 +7,46 @@ #include +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "components/gcm_driver/gcm_driver.h" + namespace gcm { -class GCMDriverAndroid { +// GCMDriver implementation for Android. +class GCMDriverAndroid : public GCMDriver { public: - // Register JNI methods + GCMDriverAndroid(); + virtual ~GCMDriverAndroid(); + + // GCMDriver implementation: + virtual void Enable() OVERRIDE; + virtual void Disable() OVERRIDE; + virtual void Register(const std::string& app_id, + const std::vector& sender_ids, + const RegisterCallback& callback) OVERRIDE; + virtual void Unregister(const std::string& app_id, + const UnregisterCallback& callback) OVERRIDE; + virtual void Send(const std::string& app_id, + const std::string& receiver_id, + const GCMClient::OutgoingMessage& message, + const SendCallback& callback) OVERRIDE; + virtual GCMClient* GetGCMClientForTesting() const OVERRIDE; + virtual bool IsStarted() const OVERRIDE; + virtual bool IsGCMClientReady() const OVERRIDE; + virtual void GetGCMStatistics(const GetGCMStatisticsCallback& callback, + bool clear_logs) OVERRIDE; + virtual void SetGCMRecording(const GetGCMStatisticsCallback& callback, + bool recording) OVERRIDE; + virtual std::string SignedInUserName() const OVERRIDE; + + // Register JNI methods. static bool RegisterBindings(JNIEnv* env); + + private: + DISALLOW_COPY_AND_ASSIGN(GCMDriverAndroid); }; } // namespace gcm -#endif // COMPONENTS_GCM_DRIVER_GCM_DRIVER_ANDROID_H \ No newline at end of file +#endif // COMPONENTS_GCM_DRIVER_GCM_DRIVER_ANDROID_H diff --git a/components/gcm_driver/gcm_driver_desktop.cc b/components/gcm_driver/gcm_driver_desktop.cc new file mode 100644 index 0000000..d57aa63 --- /dev/null +++ b/components/gcm_driver/gcm_driver_desktop.cc @@ -0,0 +1,809 @@ +// 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_desktop.h" + +#include +#include + +#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 GCMDriverDesktop::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 delayed_tasks_; + + DISALLOW_COPY_AND_ASSIGN(DelayedTaskController); +}; + +GCMDriverDesktop::DelayedTaskController::DelayedTaskController() + : ready_(false) { +} + +GCMDriverDesktop::DelayedTaskController::~DelayedTaskController() { +} + +void GCMDriverDesktop::DelayedTaskController::AddTask( + const base::Closure& task) { + delayed_tasks_.push_back(task); +} + +void GCMDriverDesktop::DelayedTaskController::SetReady() { + ready_ = true; + RunTasks(); +} + +bool GCMDriverDesktop::DelayedTaskController::CanRunTaskWithoutDelay() const { + return ready_; +} + +void GCMDriverDesktop::DelayedTaskController::RunTasks() { + DCHECK(ready_); + + for (size_t i = 0; i < delayed_tasks_.size(); ++i) + delayed_tasks_[i].Run(); + delayed_tasks_.clear(); +} + +class GCMDriverDesktop::IOWorker : public GCMClient::Delegate { + public: + // Called on UI thread. + IOWorker(const scoped_refptr& ui_thread, + const scoped_refptr& 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 gcm_client_factory, + const GCMClient::ChromeBuildInfo& chrome_build_info, + const base::FilePath& store_path, + const std::vector& account_ids, + const scoped_refptr& request_context, + const scoped_refptr blocking_task_runner); + void Start(const base::WeakPtr& service); + void Stop(); + void CheckOut(); + void Register(const std::string& app_id, + const std::vector& 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 ui_thread_; + scoped_refptr io_thread_; + + base::WeakPtr service_; + + scoped_ptr gcm_client_; + + DISALLOW_COPY_AND_ASSIGN(IOWorker); +}; + +GCMDriverDesktop::IOWorker::IOWorker( + const scoped_refptr& ui_thread, + const scoped_refptr& io_thread) + : ui_thread_(ui_thread), + io_thread_(io_thread) { + DCHECK(ui_thread_->RunsTasksOnCurrentThread()); +} + +GCMDriverDesktop::IOWorker::~IOWorker() { + DCHECK(io_thread_->RunsTasksOnCurrentThread()); +} + +void GCMDriverDesktop::IOWorker::Initialize( + scoped_ptr gcm_client_factory, + const GCMClient::ChromeBuildInfo& chrome_build_info, + const base::FilePath& store_path, + const std::vector& account_ids, + const scoped_refptr& request_context, + const scoped_refptr 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(new SystemEncryptor), + this); +} + +void GCMDriverDesktop::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(&GCMDriverDesktop::RegisterFinished, service_, app_id, + registration_id, result)); +} + +void GCMDriverDesktop::IOWorker::OnUnregisterFinished( + const std::string& app_id, + GCMClient::Result result) { + DCHECK(io_thread_->RunsTasksOnCurrentThread()); + + ui_thread_->PostTask(FROM_HERE, + base::Bind(&GCMDriverDesktop::UnregisterFinished, + service_, + app_id, + result)); +} + +void GCMDriverDesktop::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(&GCMDriverDesktop::SendFinished, service_, app_id, message_id, + result)); +} + +void GCMDriverDesktop::IOWorker::OnMessageReceived( + const std::string& app_id, + const GCMClient::IncomingMessage& message) { + DCHECK(io_thread_->RunsTasksOnCurrentThread()); + + ui_thread_->PostTask( + FROM_HERE, + base::Bind(&GCMDriverDesktop::MessageReceived, + service_, + app_id, + message)); +} + +void GCMDriverDesktop::IOWorker::OnMessagesDeleted(const std::string& app_id) { + DCHECK(io_thread_->RunsTasksOnCurrentThread()); + + ui_thread_->PostTask( + FROM_HERE, + base::Bind(&GCMDriverDesktop::MessagesDeleted, service_, app_id)); +} + +void GCMDriverDesktop::IOWorker::OnMessageSendError( + const std::string& app_id, + const GCMClient::SendErrorDetails& send_error_details) { + DCHECK(io_thread_->RunsTasksOnCurrentThread()); + + ui_thread_->PostTask( + FROM_HERE, + base::Bind(&GCMDriverDesktop::MessageSendError, service_, app_id, + send_error_details)); +} + +void GCMDriverDesktop::IOWorker::OnGCMReady() { + ui_thread_->PostTask( + FROM_HERE, + base::Bind(&GCMDriverDesktop::GCMClientReady, service_)); +} + +void GCMDriverDesktop::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 GCMDriverDesktop::IOWorker::Start( + const base::WeakPtr& service) { + DCHECK(io_thread_->RunsTasksOnCurrentThread()); + + service_ = service; + gcm_client_->Start(); +} + +void GCMDriverDesktop::IOWorker::Stop() { + DCHECK(io_thread_->RunsTasksOnCurrentThread()); + + gcm_client_->Stop(); +} + +void GCMDriverDesktop::IOWorker::CheckOut() { + DCHECK(io_thread_->RunsTasksOnCurrentThread()); + + gcm_client_->CheckOut(); + + // Note that we still need to keep GCMClient instance alive since the + // GCMDriverDesktop may check in again. +} + +void GCMDriverDesktop::IOWorker::Register( + const std::string& app_id, + const std::vector& sender_ids) { + DCHECK(io_thread_->RunsTasksOnCurrentThread()); + + gcm_client_->Register(app_id, sender_ids); +} + +void GCMDriverDesktop::IOWorker::Unregister(const std::string& app_id) { + DCHECK(io_thread_->RunsTasksOnCurrentThread()); + + gcm_client_->Unregister(app_id); +} + +void GCMDriverDesktop::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 GCMDriverDesktop::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(&GCMDriverDesktop::GetGCMStatisticsFinished, service_, stats)); +} + +void GCMDriverDesktop::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(&GCMDriverDesktop::GetGCMStatisticsFinished, service_, stats)); +} + +GCMDriverDesktop::GCMDriverDesktop( + scoped_ptr gcm_client_factory, + scoped_ptr identity_provider, + const GCMClient::ChromeBuildInfo& chrome_build_info, + const base::FilePath& store_path, + const scoped_refptr& request_context, + const scoped_refptr& ui_thread, + const scoped_refptr& io_thread, + const scoped_refptr& 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 account_ids; + account_ids = identity_provider_->GetTokenService()->GetAccounts(); + + // 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(&GCMDriverDesktop::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); +} + +GCMDriverDesktop::~GCMDriverDesktop() { +} + +void GCMDriverDesktop::Shutdown() { + DCHECK(ui_thread_->RunsTasksOnCurrentThread()); + identity_provider_->RemoveObserver(this); + GCMDriver::Shutdown(); + io_thread_->DeleteSoon(FROM_HERE, io_worker_.release()); +} + +void GCMDriverDesktop::AddAppHandler(const std::string& app_id, + GCMAppHandler* handler) { + DCHECK(ui_thread_->RunsTasksOnCurrentThread()); + GCMDriver::AddAppHandler(app_id, handler); + + // Ensures that the GCM service is started when there is an interest. + EnsureStarted(); +} + +void GCMDriverDesktop::RemoveAppHandler(const std::string& app_id) { + DCHECK(ui_thread_->RunsTasksOnCurrentThread()); + GCMDriver::RemoveAppHandler(app_id); + + // Stops the GCM service when no app intends to consume it. + if (app_handlers().empty()) + Stop(); +} + +void GCMDriverDesktop::Enable() { + DCHECK(ui_thread_->RunsTasksOnCurrentThread()); + + if (gcm_enabled_) + return; + gcm_enabled_ = true; + + EnsureStarted(); +} + +void GCMDriverDesktop::Disable() { + DCHECK(ui_thread_->RunsTasksOnCurrentThread()); + + if (!gcm_enabled_) + return; + gcm_enabled_ = false; + + Stop(); +} + +void GCMDriverDesktop::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(&GCMDriverDesktop::IOWorker::Stop, + base::Unretained(io_worker_.get()))); +} + +void GCMDriverDesktop::Register(const std::string& app_id, + const std::vector& 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(&GCMDriverDesktop::DoRegister, + weak_ptr_factory_.GetWeakPtr(), + app_id, + sender_ids)); + return; + } + + DoRegister(app_id, sender_ids); +} + +void GCMDriverDesktop::DoRegister(const std::string& app_id, + const std::vector& sender_ids) { + DCHECK(ui_thread_->RunsTasksOnCurrentThread()); + std::map::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 normalized_sender_ids = sender_ids; + std::sort(normalized_sender_ids.begin(), normalized_sender_ids.end()); + + io_thread_->PostTask( + FROM_HERE, + base::Bind(&GCMDriverDesktop::IOWorker::Register, + base::Unretained(io_worker_.get()), + app_id, + normalized_sender_ids)); +} + +void GCMDriverDesktop::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(&GCMDriverDesktop::DoUnregister, + weak_ptr_factory_.GetWeakPtr(), + app_id)); + return; + } + + DoUnregister(app_id); +} + +void GCMDriverDesktop::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(&GCMDriverDesktop::IOWorker::Unregister, + base::Unretained(io_worker_.get()), + app_id)); +} + +void GCMDriverDesktop::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 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(&GCMDriverDesktop::DoSend, + weak_ptr_factory_.GetWeakPtr(), + app_id, + receiver_id, + message)); + return; + } + + DoSend(app_id, receiver_id, message); +} + +void GCMDriverDesktop::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(&GCMDriverDesktop::IOWorker::Send, + base::Unretained(io_worker_.get()), + app_id, + receiver_id, + message)); +} + +GCMClient* GCMDriverDesktop::GetGCMClientForTesting() const { + DCHECK(ui_thread_->RunsTasksOnCurrentThread()); + return io_worker_ ? io_worker_->gcm_client_for_testing() : NULL; +} + +bool GCMDriverDesktop::IsStarted() const { + DCHECK(ui_thread_->RunsTasksOnCurrentThread()); + return !account_id_.empty(); +} + +bool GCMDriverDesktop::IsGCMClientReady() const { + DCHECK(ui_thread_->RunsTasksOnCurrentThread()); + return gcm_client_ready_; +} + +void GCMDriverDesktop::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(&GCMDriverDesktop::IOWorker::GetGCMStatistics, + base::Unretained(io_worker_.get()), + clear_logs)); +} + +void GCMDriverDesktop::SetGCMRecording(const GetGCMStatisticsCallback& callback, + bool recording) { + DCHECK(ui_thread_->RunsTasksOnCurrentThread()); + + request_gcm_statistics_callback_ = callback; + io_thread_->PostTask( + FROM_HERE, + base::Bind(&GCMDriverDesktop::IOWorker::SetGCMRecording, + base::Unretained(io_worker_.get()), + recording)); +} + +void GCMDriverDesktop::OnActiveAccountLogin() { + EnsureStarted(); +} + +void GCMDriverDesktop::OnActiveAccountLogout() { + CheckOut(); +} + +GCMClient::Result GCMDriverDesktop::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(&GCMDriverDesktop::IOWorker::Start, + base::Unretained(io_worker_.get()), + weak_ptr_factory_.GetWeakPtr())); + + return GCMClient::SUCCESS; +} + +void GCMDriverDesktop::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 GCMDriverDesktop::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(&GCMDriverDesktop::IOWorker::CheckOut, + base::Unretained(io_worker_.get()))); +} + +bool GCMDriverDesktop::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 GCMDriverDesktop::RegisterFinished(const std::string& app_id, + const std::string& registration_id, + GCMClient::Result result) { + DCHECK(ui_thread_->RunsTasksOnCurrentThread()); + + std::map::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 GCMDriverDesktop::UnregisterFinished(const std::string& app_id, + GCMClient::Result result) { + DCHECK(ui_thread_->RunsTasksOnCurrentThread()); + + std::map::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 GCMDriverDesktop::SendFinished(const std::string& app_id, + const std::string& message_id, + GCMClient::Result result) { + DCHECK(ui_thread_->RunsTasksOnCurrentThread()); + + std::map, SendCallback>::iterator + callback_iter = send_callbacks_.find( + std::pair(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 GCMDriverDesktop::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 GCMDriverDesktop::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 GCMDriverDesktop::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 GCMDriverDesktop::GCMClientReady() { + DCHECK(ui_thread_->RunsTasksOnCurrentThread()); + + if (gcm_client_ready_) + return; + gcm_client_ready_ = true; + + delayed_task_controller_->SetReady(); +} + +void GCMDriverDesktop::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 GCMDriverDesktop::SignedInUserName() const { + if (IsStarted()) + return identity_provider_->GetActiveUsername(); + return std::string(); +} + +} // namespace gcm diff --git a/components/gcm_driver/gcm_driver_desktop.h b/components/gcm_driver/gcm_driver_desktop.h new file mode 100644 index 0000000..1a6d882 --- /dev/null +++ b/components/gcm_driver/gcm_driver_desktop.h @@ -0,0 +1,171 @@ +// 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_DESKTOP_H_ +#define COMPONENTS_GCM_DRIVER_GCM_DRIVER_DESKTOP_H_ + +#include +#include +#include + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "components/gcm_driver/gcm_driver.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; + +// GCMDriver implementation for desktop and Chrome OS, using GCMClient. +class GCMDriverDesktop : public GCMDriver, public IdentityProvider::Observer { + public: + GCMDriverDesktop( + scoped_ptr gcm_client_factory, + scoped_ptr identity_provider, + const GCMClient::ChromeBuildInfo& chrome_build_info, + const base::FilePath& store_path, + const scoped_refptr& request_context, + const scoped_refptr& ui_thread, + const scoped_refptr& io_thread, + const scoped_refptr& blocking_task_runner); + virtual ~GCMDriverDesktop(); + + // GCMDriver overrides: + virtual void Shutdown() OVERRIDE; + virtual void AddAppHandler(const std::string& app_id, + GCMAppHandler* handler) OVERRIDE; + virtual void RemoveAppHandler(const std::string& app_id) OVERRIDE; + + // GCMDriver implementation: + virtual void Enable() OVERRIDE; + virtual void Disable() OVERRIDE; + virtual void Register(const std::string& app_id, + const std::vector& sender_ids, + const RegisterCallback& callback) OVERRIDE; + virtual void Unregister(const std::string& app_id, + const UnregisterCallback& callback) OVERRIDE; + virtual void Send(const std::string& app_id, + const std::string& receiver_id, + const GCMClient::OutgoingMessage& message, + const SendCallback& callback) OVERRIDE; + virtual GCMClient* GetGCMClientForTesting() const OVERRIDE; + virtual bool IsStarted() const OVERRIDE; + virtual bool IsGCMClientReady() const OVERRIDE; + virtual void GetGCMStatistics(const GetGCMStatisticsCallback& callback, + bool clear_logs) OVERRIDE; + virtual void SetGCMRecording(const GetGCMStatisticsCallback& callback, + bool recording) OVERRIDE; + virtual std::string SignedInUserName() const OVERRIDE; + + // IdentityProvider::Observer implementation: + virtual void OnActiveAccountLogin() OVERRIDE; + virtual void OnActiveAccountLogout() OVERRIDE; + + 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& 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(); + + 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 identity_provider_; + scoped_refptr ui_thread_; + scoped_refptr io_thread_; + + scoped_ptr delayed_task_controller_; + + // For all the work occurring on the IO thread. Must be destroyed on the IO + // thread. + scoped_ptr io_worker_; + + // Callback map (from app_id to callback) for Register. + std::map register_callbacks_; + + // Callback map (from app_id to callback) for Unregister. + std::map unregister_callbacks_; + + // Callback map (from to callback) for Send. + std::map, SendCallback> send_callbacks_; + + // Callback for GetGCMStatistics. + GetGCMStatisticsCallback request_gcm_statistics_callback_; + + // Used to pass a weak pointer to the IO worker. + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(GCMDriverDesktop); +}; + +} // namespace gcm + +#endif // COMPONENTS_GCM_DRIVER_GCM_DRIVER_DESKTOP_H_ diff --git a/components/gcm_driver/gcm_driver_desktop_unittest.cc b/components/gcm_driver/gcm_driver_desktop_unittest.cc new file mode 100644 index 0000000..d4694cfb --- /dev/null +++ b/components/gcm_driver/gcm_driver_desktop_unittest.cc @@ -0,0 +1,901 @@ +// 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_desktop.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 ToSenderList(const std::string& sender_ids) { + std::vector 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& 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 identity_provider_owner_; + FakeIdentityProvider* identity_provider_; + scoped_refptr task_runner_; + base::MessageLoopForUI message_loop_; + base::Thread io_thread_; + scoped_ptr driver_; + scoped_ptr 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(driver_->GetGCMClientForTesting()); +} + +void GCMDriverTest::CreateDriver( + FakeGCMClient::StartMode gcm_client_start_mode) { + scoped_refptr request_context = + new net::TestURLRequestContextGetter(io_thread_.message_loop_proxy()); + // TODO(johnme): Need equivalent test coverage of GCMDriverAndroid. + driver_.reset(new GCMDriverDesktop( + scoped_ptr(new FakeGCMClientFactory( + gcm_client_start_mode, + base::MessageLoopProxy::current(), + io_thread_.message_loop_proxy())).Pass(), + identity_provider_owner_.PassAs(), + 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& 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 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 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 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 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 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 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 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 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 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 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 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 diff --git a/components/gcm_driver/gcm_driver_unittest.cc b/components/gcm_driver/gcm_driver_unittest.cc deleted file mode 100644 index 52f6abb..0000000 --- a/components/gcm_driver/gcm_driver_unittest.cc +++ /dev/null @@ -1,900 +0,0 @@ -// 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 ToSenderList(const std::string& sender_ids) { - std::vector 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& 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 identity_provider_owner_; - FakeIdentityProvider* identity_provider_; - scoped_refptr task_runner_; - base::MessageLoopForUI message_loop_; - base::Thread io_thread_; - scoped_ptr driver_; - scoped_ptr 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(driver_->GetGCMClientForTesting()); -} - -void GCMDriverTest::CreateDriver( - FakeGCMClient::StartMode gcm_client_start_mode) { - scoped_refptr request_context = - new net::TestURLRequestContextGetter(io_thread_.message_loop_proxy()); - driver_.reset(new GCMDriver( - scoped_ptr(new FakeGCMClientFactory( - gcm_client_start_mode, - base::MessageLoopProxy::current(), - io_thread_.message_loop_proxy())).Pass(), - identity_provider_owner_.PassAs(), - 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& 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 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 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 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 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 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 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 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 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 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 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 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 -- cgit v1.1