// 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 <utility> #include "base/bind.h" #include "base/bind_helpers.h" #include "base/files/file_path.h" #include "base/location.h" #include "base/logging.h" #include "base/sequenced_task_runner.h" #include "base/threading/sequenced_worker_pool.h" #include "components/gcm_driver/gcm_app_handler.h" #include "components/gcm_driver/gcm_client_factory.h" #include "components/gcm_driver/system_encryptor.h" #include "net/base/ip_endpoint.h" #include "net/url_request/url_request_context_getter.h" namespace gcm { namespace { // Empty string is reserved for the default app handler. const char kDefaultAppHandler[] = ""; } // namespace // 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<base::Closure> 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<base::SequencedTaskRunner>& ui_thread, const scoped_refptr<base::SequencedTaskRunner>& io_thread); virtual ~IOWorker(); // Overridden from GCMClient::Delegate: // Called on IO thread. virtual void OnRegisterFinished(const std::string& app_id, const std::string& registration_id, GCMClient::Result result) OVERRIDE; virtual void OnUnregisterFinished(const std::string& app_id, GCMClient::Result result) OVERRIDE; virtual void OnSendFinished(const std::string& app_id, const std::string& message_id, GCMClient::Result result) OVERRIDE; virtual void OnMessageReceived( const std::string& app_id, const GCMClient::IncomingMessage& message) OVERRIDE; virtual void OnMessagesDeleted(const std::string& app_id) OVERRIDE; virtual void OnMessageSendError( const std::string& app_id, const GCMClient::SendErrorDetails& send_error_details) OVERRIDE; virtual void OnGCMReady() OVERRIDE; virtual void OnActivityRecorded() OVERRIDE; virtual void OnConnected(const net::IPEndPoint& ip_endpoint) OVERRIDE; virtual void OnDisconnected() OVERRIDE; // Called on IO thread. void Initialize( scoped_ptr<GCMClientFactory> gcm_client_factory, const GCMClient::ChromeBuildInfo& chrome_build_info, const base::FilePath& store_path, const scoped_refptr<net::URLRequestContextGetter>& request_context, const scoped_refptr<base::SequencedTaskRunner> blocking_task_runner); void Start(const base::WeakPtr<GCMDriverDesktop>& service); void Stop(); void CheckOut(); void Register(const std::string& app_id, const std::vector<std::string>& sender_ids); void Unregister(const std::string& app_id); void Send(const std::string& app_id, const std::string& receiver_id, const GCMClient::OutgoingMessage& message); void GetGCMStatistics(bool clear_logs); void SetGCMRecording(bool recording); // For testing purpose. Can be called from UI thread. Use with care. GCMClient* gcm_client_for_testing() const { return gcm_client_.get(); } private: scoped_refptr<base::SequencedTaskRunner> ui_thread_; scoped_refptr<base::SequencedTaskRunner> io_thread_; base::WeakPtr<GCMDriverDesktop> service_; scoped_ptr<GCMClient> gcm_client_; DISALLOW_COPY_AND_ASSIGN(IOWorker); }; GCMDriverDesktop::IOWorker::IOWorker( const scoped_refptr<base::SequencedTaskRunner>& ui_thread, const scoped_refptr<base::SequencedTaskRunner>& io_thread) : ui_thread_(ui_thread), io_thread_(io_thread) { DCHECK(ui_thread_->RunsTasksOnCurrentThread()); } GCMDriverDesktop::IOWorker::~IOWorker() { DCHECK(io_thread_->RunsTasksOnCurrentThread()); } void GCMDriverDesktop::IOWorker::Initialize( scoped_ptr<GCMClientFactory> gcm_client_factory, const GCMClient::ChromeBuildInfo& chrome_build_info, const base::FilePath& store_path, const scoped_refptr<net::URLRequestContextGetter>& request_context, const scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) { DCHECK(io_thread_->RunsTasksOnCurrentThread()); gcm_client_ = gcm_client_factory->BuildInstance(); gcm_client_->Initialize(chrome_build_info, store_path, blocking_task_runner, request_context, make_scoped_ptr<Encryptor>(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::OnConnected( const net::IPEndPoint& ip_endpoint) { ui_thread_->PostTask(FROM_HERE, base::Bind(&GCMDriverDesktop::OnConnected, service_, ip_endpoint)); } void GCMDriverDesktop::IOWorker::OnDisconnected() { ui_thread_->PostTask(FROM_HERE, base::Bind(&GCMDriverDesktop::OnDisconnected, service_)); } void GCMDriverDesktop::IOWorker::Start( const base::WeakPtr<GCMDriverDesktop>& 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<std::string>& 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<GCMClientFactory> gcm_client_factory, const GCMClient::ChromeBuildInfo& chrome_build_info, const base::FilePath& store_path, const scoped_refptr<net::URLRequestContextGetter>& request_context, const scoped_refptr<base::SequencedTaskRunner>& ui_thread, const scoped_refptr<base::SequencedTaskRunner>& io_thread, const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner) : signed_in_(false), gcm_started_(false), gcm_enabled_(true), connected_(false), ui_thread_(ui_thread), io_thread_(io_thread), weak_ptr_factory_(this) { // 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, request_context, blocking_task_runner)); } GCMDriverDesktop::~GCMDriverDesktop() { } void GCMDriverDesktop::Shutdown() { DCHECK(ui_thread_->RunsTasksOnCurrentThread()); GCMDriver::Shutdown(); io_thread_->DeleteSoon(FROM_HERE, io_worker_.release()); } void GCMDriverDesktop::OnSignedIn() { signed_in_ = true; EnsureStarted(); } void GCMDriverDesktop::Purge() { 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. signed_in_ = false; RemoveCachedData(); io_thread_->PostTask(FROM_HERE, base::Bind(&GCMDriverDesktop::IOWorker::CheckOut, base::Unretained(io_worker_.get()))); } 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 (!gcm_started_) return; RemoveCachedData(); io_thread_->PostTask( FROM_HERE, base::Bind(&GCMDriverDesktop::IOWorker::Stop, base::Unretained(io_worker_.get()))); } void GCMDriverDesktop::RegisterImpl( const std::string& app_id, const std::vector<std::string>& sender_ids) { // 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<std::string>& sender_ids) { DCHECK(ui_thread_->RunsTasksOnCurrentThread()); if (!HasRegisterCallback(app_id)) { // The callback could have been removed when the app is uninstalled. return; } io_thread_->PostTask( FROM_HERE, base::Bind(&GCMDriverDesktop::IOWorker::Register, base::Unretained(io_worker_.get()), app_id, sender_ids)); } void GCMDriverDesktop::UnregisterImpl(const std::string& app_id) { // 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::SendImpl(const std::string& app_id, const std::string& receiver_id, const GCMClient::OutgoingMessage& message) { // 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 gcm_started_; } bool GCMDriverDesktop::IsConnected() const { return connected_; } 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)); } GCMClient::Result GCMDriverDesktop::EnsureStarted() { DCHECK(ui_thread_->RunsTasksOnCurrentThread()); if (gcm_started_) return GCMClient::SUCCESS; if (!gcm_enabled_) return GCMClient::GCM_DISABLED; // Have any app requested the service? if (app_handlers().empty()) return GCMClient::UNKNOWN_ERROR; // TODO(jianli): To be removed when sign-in enforcement is dropped. if (!signed_in_) return GCMClient::NOT_SIGNED_IN; 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())); gcm_started_ = true; 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(); gcm_started_ = false; delayed_task_controller_.reset(); ClearCallbacks(); } void GCMDriverDesktop::MessageReceived( const std::string& app_id, const GCMClient::IncomingMessage& message) { DCHECK(ui_thread_->RunsTasksOnCurrentThread()); // Drop the event if the service has been stopped. if (!gcm_started_) return; GetAppHandler(app_id)->OnMessage(app_id, message); } void GCMDriverDesktop::MessagesDeleted(const std::string& app_id) { DCHECK(ui_thread_->RunsTasksOnCurrentThread()); // Drop the event if the service has been stopped. if (!gcm_started_) 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 the service has been stopped. if (!gcm_started_) return; GetAppHandler(app_id)->OnSendError(app_id, send_error_details); } void GCMDriverDesktop::GCMClientReady() { DCHECK(ui_thread_->RunsTasksOnCurrentThread()); delayed_task_controller_->SetReady(); } void GCMDriverDesktop::OnConnected(const net::IPEndPoint& ip_endpoint) { DCHECK(ui_thread_->RunsTasksOnCurrentThread()); connected_ = true; // Drop the event if signed out. if (!signed_in_) return; const GCMAppHandlerMap& app_handler_map = app_handlers(); for (GCMAppHandlerMap::const_iterator iter = app_handler_map.begin(); iter != app_handler_map.end(); ++iter) { iter->second->OnConnected(ip_endpoint); } GetAppHandler(kDefaultAppHandler)->OnConnected(ip_endpoint); } void GCMDriverDesktop::OnDisconnected() { DCHECK(ui_thread_->RunsTasksOnCurrentThread()); connected_ = false; // Drop the event if signed out. if (!signed_in_) return; const GCMAppHandlerMap& app_handler_map = app_handlers(); for (GCMAppHandlerMap::const_iterator iter = app_handler_map.begin(); iter != app_handler_map.end(); ++iter) { iter->second->OnDisconnected(); } GetAppHandler(kDefaultAppHandler)->OnDisconnected(); } void GCMDriverDesktop::GetGCMStatisticsFinished( const 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."; } } // namespace gcm