普通文本  |  716行  |  21.82 KB

// 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