summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorckehoe <ckehoe@chromium.org>2014-11-07 17:01:15 -0800
committerCommit bot <commit-bot@chromium.org>2014-11-08 01:01:45 +0000
commit19b4f6371c68fe1627c2b75ca5ec8a4061796e58 (patch)
tree5d6ef26572e895fa8cf6fe5957484f427162d33c
parentaf5a5768de0ef5563228a0a8c0dd519c92c12a86 (diff)
downloadchromium_src-19b4f6371c68fe1627c2b75ca5ec8a4061796e58.zip
chromium_src-19b4f6371c68fe1627c2b75ca5ec8a4061796e58.tar.gz
chromium_src-19b4f6371c68fe1627c2b75ca5ec8a4061796e58.tar.bz2
Adding GCM support to the copresence component. The Copresence server can push directives (and messages, though they are not yet handled) over GCM.
BUG=424253,425681 Review URL: https://codereview.chromium.org/710513004 Cr-Commit-Position: refs/heads/master@{#303342}
-rw-r--r--chrome/browser/extensions/api/copresence/copresence_api.cc7
-rw-r--r--chrome/browser/extensions/api/copresence/copresence_api.h5
-rw-r--r--components/components_tests.gyp1
-rw-r--r--components/copresence.gypi11
-rw-r--r--components/copresence/BUILD.gn2
-rw-r--r--components/copresence/DEPS1
-rw-r--r--components/copresence/copresence_manager_impl.cc13
-rw-r--r--components/copresence/copresence_manager_impl.h10
-rw-r--r--components/copresence/handlers/gcm_handler.cc155
-rw-r--r--components/copresence/handlers/gcm_handler.h78
-rw-r--r--components/copresence/handlers/gcm_handler_unittest.cc71
-rw-r--r--components/copresence/proto/BUILD.gn1
-rw-r--r--components/copresence/proto/data.proto4
-rw-r--r--components/copresence/proto/push_message.proto20
-rw-r--r--components/copresence/public/copresence_delegate.h8
-rw-r--r--components/copresence/rpc/rpc_handler.cc117
-rw-r--r--components/copresence/rpc/rpc_handler.h12
-rw-r--r--components/copresence/rpc/rpc_handler_unittest.cc48
-rw-r--r--components/copresence/test/fake_directive_handler.cc32
-rw-r--r--components/copresence/test/fake_directive_handler.h49
20 files changed, 570 insertions, 75 deletions
diff --git a/chrome/browser/extensions/api/copresence/copresence_api.cc b/chrome/browser/extensions/api/copresence/copresence_api.cc
index 7e91674..f4ae23e 100644
--- a/chrome/browser/extensions/api/copresence/copresence_api.cc
+++ b/chrome/browser/extensions/api/copresence/copresence_api.cc
@@ -7,6 +7,8 @@
#include "base/lazy_instance.h"
#include "base/memory/linked_ptr.h"
#include "chrome/browser/copresence/chrome_whispernet_client.h"
+#include "chrome/browser/services/gcm/gcm_profile_service.h"
+#include "chrome/browser/services/gcm/gcm_profile_service_factory.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/common/extensions/api/copresence.h"
#include "components/copresence/copresence_manager_impl.h"
@@ -145,6 +147,11 @@ copresence::WhispernetClient* CopresenceService::GetWhispernetClient() {
return whispernet_client();
}
+gcm::GCMDriver* CopresenceService::GetGCMDriver() {
+ return gcm::GCMProfileServiceFactory::GetForProfile(browser_context_)
+ ->driver();
+}
+
template <>
void
BrowserContextKeyedAPIFactory<CopresenceService>::DeclareFactoryDependencies() {
diff --git a/chrome/browser/extensions/api/copresence/copresence_api.h b/chrome/browser/extensions/api/copresence/copresence_api.h
index b7103cf..d49c878 100644
--- a/chrome/browser/extensions/api/copresence/copresence_api.h
+++ b/chrome/browser/extensions/api/copresence/copresence_api.h
@@ -24,6 +24,10 @@ class CopresenceManager;
class WhispernetClient;
}
+namespace gcm {
+class GCMDriver;
+}
+
namespace extensions {
class CopresenceService : public BrowserContextKeyedAPI,
@@ -73,6 +77,7 @@ class CopresenceService : public BrowserContextKeyedAPI,
const std::string GetPlatformVersionString() const override;
const std::string GetAPIKey(const std::string& app_id) const override;
copresence::WhispernetClient* GetWhispernetClient() override;
+ gcm::GCMDriver* GetGCMDriver() override;
// BrowserContextKeyedAPI implementation.
static const char* service_name() { return "CopresenceService"; }
diff --git a/components/components_tests.gyp b/components/components_tests.gyp
index 5d543bb..daa4ebf 100644
--- a/components/components_tests.gyp
+++ b/components/components_tests.gyp
@@ -699,6 +699,7 @@
'copresence/handlers/audio/audio_directive_handler_unittest.cc',
'copresence/handlers/audio/audio_directive_list_unittest.cc',
'copresence/handlers/directive_handler_unittest.cc',
+ 'copresence/handlers/gcm_handler_unittest.cc',
'copresence/mediums/audio/audio_manager_unittest.cc',
'copresence/mediums/audio/audio_player_unittest.cc',
'copresence/mediums/audio/audio_recorder_unittest.cc',
diff --git a/components/copresence.gypi b/components/copresence.gypi
index cf5c10e..1a789ce 100644
--- a/components/copresence.gypi
+++ b/components/copresence.gypi
@@ -33,6 +33,8 @@
'copresence/handlers/audio/tick_clock_ref_counted.h',
'copresence/handlers/directive_handler.cc',
'copresence/handlers/directive_handler.h',
+ 'copresence/handlers/gcm_handler.cc',
+ 'copresence/handlers/gcm_handler.h',
'copresence/mediums/audio/audio_manager.h',
'copresence/mediums/audio/audio_manager_impl.cc',
'copresence/mediums/audio/audio_manager_impl.h',
@@ -59,15 +61,23 @@
{
'target_name': 'copresence_test_support',
'type': 'static_library',
+ 'dependencies': [
+ 'copresence_proto',
+ ],
'include_dirs': [
'..',
],
'sources': [
'copresence/test/audio_test_support.cc',
'copresence/test/audio_test_support.h',
+ 'copresence/test/fake_directive_handler.cc',
+ 'copresence/test/fake_directive_handler.h',
'copresence/test/stub_whispernet_client.cc',
'copresence/test/stub_whispernet_client.h',
],
+ 'export_dependent_settings': [
+ 'copresence_proto',
+ ],
},
{
# Protobuf compiler / generate rule for copresence.
@@ -82,6 +92,7 @@
'copresence/proto/data.proto',
'copresence/proto/enums.proto',
'copresence/proto/identity.proto',
+ 'copresence/proto/push_message.proto',
'copresence/proto/rpcs.proto',
],
'variables': {
diff --git a/components/copresence/BUILD.gn b/components/copresence/BUILD.gn
index 47a5acb..7c82acb 100644
--- a/components/copresence/BUILD.gn
+++ b/components/copresence/BUILD.gn
@@ -17,6 +17,8 @@ static_library("copresence") {
"handlers/audio/tick_clock_ref_counted.h",
"handlers/directive_handler.cc",
"handlers/directive_handler.h",
+ "handlers/gcm_handler.cc",
+ "handlers/gcm_handler.h",
"mediums/audio/audio_manager.h",
"mediums/audio/audio_manager_impl.cc",
"mediums/audio/audio_manager_impl.h",
diff --git a/components/copresence/DEPS b/components/copresence/DEPS
index dec5262..959832a 100644
--- a/components/copresence/DEPS
+++ b/components/copresence/DEPS
@@ -1,5 +1,6 @@
include_rules = [
"+base",
+ "+components/gcm_driver",
"+content/public/browser",
"+content/public/test",
"+google_apis",
diff --git a/components/copresence/copresence_manager_impl.cc b/components/copresence/copresence_manager_impl.cc
index 265b042..2d5828a 100644
--- a/components/copresence/copresence_manager_impl.cc
+++ b/components/copresence/copresence_manager_impl.cc
@@ -10,6 +10,7 @@
#include "base/strings/stringprintf.h"
#include "base/timer/timer.h"
#include "components/copresence/handlers/directive_handler.h"
+#include "components/copresence/handlers/gcm_handler.h"
#include "components/copresence/proto/rpcs.pb.h"
#include "components/copresence/public/whispernet_client.h"
#include "components/copresence/rpc/rpc_handler.h"
@@ -31,16 +32,22 @@ CopresenceManagerImpl::CopresenceManagerImpl(CopresenceDelegate* delegate)
&CopresenceManagerImpl::WhispernetInitComplete,
// This callback gets cancelled when we are destroyed.
base::Unretained(this))),
+ init_failed_(false),
directive_handler_(new DirectiveHandler),
poll_timer_(new base::RepeatingTimer<CopresenceManagerImpl>),
- audio_check_timer_(new base::RepeatingTimer<CopresenceManagerImpl>),
- init_failed_(false) {
+ audio_check_timer_(new base::RepeatingTimer<CopresenceManagerImpl>) {
DCHECK(delegate_);
DCHECK(delegate_->GetWhispernetClient());
delegate_->GetWhispernetClient()->Initialize(
whispernet_init_callback_.callback());
- rpc_handler_.reset(new RpcHandler(delegate_, directive_handler_.get()));
+ if (delegate->GetGCMDriver())
+ gcm_handler_.reset(new GCMHandler(delegate->GetGCMDriver(),
+ directive_handler_.get()));
+
+ rpc_handler_.reset(new RpcHandler(delegate,
+ directive_handler_.get(),
+ gcm_handler_.get()));
}
CopresenceManagerImpl::~CopresenceManagerImpl() {
diff --git a/components/copresence/copresence_manager_impl.h b/components/copresence/copresence_manager_impl.h
index 12bc051..d91e7ae 100644
--- a/components/copresence/copresence_manager_impl.h
+++ b/components/copresence/copresence_manager_impl.h
@@ -23,6 +23,7 @@ class URLContextGetter;
namespace copresence {
class DirectiveHandler;
+class GCMHandler;
class ReportRequest;
class RpcHandler;
class WhispernetClient;
@@ -61,16 +62,17 @@ class CopresenceManagerImpl : public CopresenceManager {
// does not provide a way to unregister its init callback.
base::CancelableCallback<void(bool)> whispernet_init_callback_;
- // The |directive handler_| needs to destruct before |rpc_handler_|, do not
- // change this order.
+ bool init_failed_;
+
+ // The GCMHandler must destruct before the DirectiveHandler,
+ // which must destruct before the RpcHandler. Do not change this order.
scoped_ptr<RpcHandler> rpc_handler_;
scoped_ptr<DirectiveHandler> directive_handler_;
+ scoped_ptr<GCMHandler> gcm_handler_;
scoped_ptr<base::Timer> poll_timer_;
scoped_ptr<base::Timer> audio_check_timer_;
- bool init_failed_;
-
DISALLOW_COPY_AND_ASSIGN(CopresenceManagerImpl);
};
diff --git a/components/copresence/handlers/gcm_handler.cc b/components/copresence/handlers/gcm_handler.cc
new file mode 100644
index 0000000..023380b
--- /dev/null
+++ b/components/copresence/handlers/gcm_handler.cc
@@ -0,0 +1,155 @@
+// 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/copresence/handlers/gcm_handler.h"
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "components/copresence/handlers/directive_handler.h"
+#include "components/copresence/proto/push_message.pb.h"
+#include "components/gcm_driver/gcm_driver.h"
+
+using gcm::GCMClient;
+
+namespace {
+
+// TODO(ckehoe): Move this to a common library.
+bool Base64Decode(std::string data, std::string* out) {
+ // Convert from URL-safe.
+ base::ReplaceChars(data, "-", "+", &data);
+ base::ReplaceChars(data, "_", "/", &data);
+
+ // Add padding if needed.
+ while (data.size() % 4)
+ data.push_back('=');
+
+ // Decode.
+ return base::Base64Decode(data, out);
+}
+
+} // namespace
+
+namespace copresence {
+
+const char GCMHandler::kCopresenceAppId[] =
+ "com.google.android.gms.location.copresence";
+const char GCMHandler::kCopresenceSenderId[] = "745476177629";
+const char GCMHandler::kGcmMessageKey[] = "PUSH_MESSAGE";
+
+
+// Public functions.
+
+GCMHandler::GCMHandler(gcm::GCMDriver* gcm_driver,
+ DirectiveHandler* directive_handler)
+ : driver_(gcm_driver),
+ directive_handler_(directive_handler),
+ registration_callback_(base::Bind(&GCMHandler::RegistrationComplete,
+ AsWeakPtr())) {
+ DCHECK(driver_);
+ DCHECK(directive_handler_);
+
+ driver_->AddAppHandler(kCopresenceAppId, this);
+ driver_->Register(kCopresenceAppId,
+ std::vector<std::string>(1, kCopresenceSenderId),
+ registration_callback_);
+}
+
+GCMHandler::~GCMHandler() {
+ if (driver_)
+ driver_->RemoveAppHandler(kCopresenceAppId);
+}
+
+void GCMHandler::GetGcmId(const RegistrationCallback& callback) {
+ if (gcm_id_.empty()) {
+ pending_id_requests_.push_back(callback);
+ } else {
+ callback.Run(gcm_id_);
+ }
+}
+
+void GCMHandler::ShutdownHandler() {
+ // The GCMDriver is going away. Make sure we don't try to contact it.
+ driver_ = nullptr;
+}
+
+void GCMHandler::OnMessage(const std::string& app_id,
+ const GCMClient::IncomingMessage& message) {
+ DCHECK_EQ(kCopresenceAppId, app_id);
+ DVLOG(2) << "Incoming GCM message";
+
+ const auto& content = message.data.find(kGcmMessageKey);
+ if (content == message.data.end()) {
+ LOG(ERROR) << "GCM message missing data key";
+ return;
+ }
+
+ std::string serialized_message;
+ if (!Base64Decode(content->second, &serialized_message)) {
+ LOG(ERROR) << "Couldn't decode GCM message";
+ return;
+ }
+
+ PushMessage push_message;
+ if (!push_message.ParseFromString(serialized_message)) {
+ LOG(ERROR) << "GCM message contained invalid proto";
+ return;
+ }
+
+ if (push_message.type() != PushMessage::REPORT) {
+ DVLOG(2) << "Discarding non-report GCM message";
+ return;
+ }
+
+ DVLOG(3) << "Processing " << push_message.report().directive_size()
+ << " directive(s) from GCM message";
+ for (const Directive& directive : push_message.report().directive())
+ directive_handler_->AddDirective(directive);
+
+ int message_count = push_message.report().subscribed_message_size();
+ LOG_IF(WARNING, message_count > 0)
+ << "Discarding " << message_count << " copresence messages sent via GCM";
+}
+
+void GCMHandler::OnMessagesDeleted(const std::string& app_id) {
+ DCHECK_EQ(kCopresenceAppId, app_id);
+ DVLOG(2) << "GCM message overflow reported";
+}
+
+void GCMHandler::OnSendError(
+ const std::string& /* app_id */,
+ const GCMClient::SendErrorDetails& /* send_error_details */) {
+ NOTREACHED() << "Copresence clients should not be sending GCM messages";
+}
+
+void GCMHandler::OnSendAcknowledged(const std::string& /* app_id */,
+ const std::string& /* message_id */) {
+ NOTREACHED() << "Copresence clients should not be sending GCM messages";
+}
+
+bool GCMHandler::CanHandle(const std::string& app_id) const {
+ return app_id == kCopresenceAppId;
+}
+
+
+// Private functions.
+
+void GCMHandler::RegistrationComplete(const std::string& registration_id,
+ GCMClient::Result result) {
+ if (result == GCMClient::SUCCESS) {
+ DVLOG(2) << "GCM registration successful. ID: " << registration_id;
+ gcm_id_ = registration_id;
+ } else {
+ LOG(ERROR) << "GCM registration failed with error " << result;
+ }
+
+ for (const RegistrationCallback& callback : pending_id_requests_) {
+ callback.Run(result == GCMClient::SUCCESS ?
+ registration_id : std::string());
+ }
+ pending_id_requests_.clear();
+}
+
+} // namespace copresence
diff --git a/components/copresence/handlers/gcm_handler.h b/components/copresence/handlers/gcm_handler.h
new file mode 100644
index 0000000..f69d8a4
--- /dev/null
+++ b/components/copresence/handlers/gcm_handler.h
@@ -0,0 +1,78 @@
+// 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_COPRESENCE_HANDLERS_GCM_HANDLER_H_
+#define COMPONENTS_COPRESENCE_HANDLERS_GCM_HANDLER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "components/gcm_driver/gcm_app_handler.h"
+#include "components/gcm_driver/gcm_client.h"
+
+namespace gcm {
+class GCMDriver;
+}
+
+namespace copresence {
+
+class DirectiveHandler;
+
+// This class handles GCM messages from the Copresence server.
+class GCMHandler final : public gcm::GCMAppHandler,
+ public base::SupportsWeakPtr<GCMHandler> {
+ public:
+ // Callback to report that registration has completed.
+ // Returns an empty ID if registration failed.
+ using RegistrationCallback = base::Callback<void(const std::string&)>;
+
+ static const char kCopresenceAppId[];
+ static const char kCopresenceSenderId[];
+ static const char kGcmMessageKey[];
+
+ // |gcm_driver| is required, but may disappear if we get a ShutdownHandler()
+ // call first. |directive_handler| must outlive us. The caller owns both.
+ GCMHandler(gcm::GCMDriver* gcm_driver,
+ DirectiveHandler* directive_handler);
+ ~GCMHandler() override;
+
+ // Request the GCM ID. It may be returned now or later, via the callback.
+ void GetGcmId(const RegistrationCallback& callback);
+
+ // GCMAppHandler overrides
+ void ShutdownHandler() override;
+ void OnMessage(const std::string& app_id,
+ const gcm::GCMClient::IncomingMessage& message) override;
+ void OnMessagesDeleted(const std::string& app_id) override;
+ void OnSendError(
+ const std::string& /* app_id */,
+ const gcm::GCMClient::SendErrorDetails& /* send_error_details */)
+ override;
+ void OnSendAcknowledged(const std::string& /* app_id */,
+ const std::string& /* message_id */) override;
+ bool CanHandle(const std::string& app_id) const override;
+
+ private:
+ // GCM Registration has finished. Notify clients as appropriate.
+ void RegistrationComplete(const std::string& registration_id,
+ gcm::GCMClient::Result result);
+
+ gcm::GCMDriver* driver_;
+ DirectiveHandler* const directive_handler_;
+
+ // TODO(ckehoe): Change this to a CancelableCallback.
+ base::Callback<void(const std::string&,
+ gcm::GCMClient::Result)> registration_callback_;
+
+ std::string gcm_id_;
+ std::vector<RegistrationCallback> pending_id_requests_;
+
+ DISALLOW_COPY_AND_ASSIGN(GCMHandler);
+};
+
+} // namespace copresence
+
+#endif // COMPONENTS_COPRESENCE_HANDLERS_GCM_HANDLER_H_
diff --git a/components/copresence/handlers/gcm_handler_unittest.cc b/components/copresence/handlers/gcm_handler_unittest.cc
new file mode 100644
index 0000000..ee36291
--- /dev/null
+++ b/components/copresence/handlers/gcm_handler_unittest.cc
@@ -0,0 +1,71 @@
+// 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/copresence/handlers/gcm_handler.h"
+
+#include "base/base64.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+#include "components/copresence/proto/push_message.pb.h"
+#include "components/copresence/test/fake_directive_handler.h"
+#include "components/gcm_driver/fake_gcm_driver.h"
+#include "components/gcm_driver/gcm_client.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using gcm::GCMClient;
+
+namespace {
+
+// TODO(ckehoe): Move this to a central place.
+std::string ToUrlSafe(std::string token) {
+ base::ReplaceChars(token, "+", "-", &token);
+ base::ReplaceChars(token, "/", "_", &token);
+ return token;
+}
+
+} // namespace
+
+
+namespace copresence {
+
+class GCMHandlerTest : public testing::Test {
+ public:
+ GCMHandlerTest()
+ : driver_(new gcm::FakeGCMDriver),
+ directive_handler_(new FakeDirectiveHandler),
+ gcm_handler_(driver_.get(), directive_handler_.get()) {
+ }
+
+ protected:
+ scoped_ptr<gcm::GCMDriver> driver_;
+ scoped_ptr<FakeDirectiveHandler> directive_handler_;
+ GCMHandler gcm_handler_;
+};
+
+TEST_F(GCMHandlerTest, OnMessage) {
+ // Create a PushMessage.
+ PushMessage push_message;
+ push_message.set_type(PushMessage::REPORT);
+ Report* report = push_message.mutable_report();
+ report->add_directive()->set_subscription_id("subscription 1");
+ report->add_directive()->set_subscription_id("subscription 2");
+
+ // Encode it.
+ std::string serialized_proto;
+ std::string encoded_proto;
+ push_message.SerializeToString(&serialized_proto);
+ base::Base64Encode(serialized_proto, &encoded_proto);
+
+ // Send it in a GCM message.
+ GCMClient::IncomingMessage gcm_message;
+ gcm_message.data[GCMHandler::kGcmMessageKey] = ToUrlSafe(encoded_proto);
+ gcm_handler_.OnMessage(GCMHandler::kCopresenceAppId, gcm_message);
+
+ // Check that the correct directives were passed along.
+ EXPECT_THAT(directive_handler_->added_directives(),
+ testing::ElementsAre("subscription 1", "subscription 2"));
+}
+
+} // namespace copresence
+
diff --git a/components/copresence/proto/BUILD.gn b/components/copresence/proto/BUILD.gn
index 87812dd..d7cd331 100644
--- a/components/copresence/proto/BUILD.gn
+++ b/components/copresence/proto/BUILD.gn
@@ -11,6 +11,7 @@ proto_library("proto") {
"data.proto",
"enums.proto",
"identity.proto",
+ "push_message.proto",
"rpcs.proto",
]
}
diff --git a/components/copresence/proto/data.proto b/components/copresence/proto/data.proto
index ef8e0ee..b7acb42e 100644
--- a/components/copresence/proto/data.proto
+++ b/components/copresence/proto/data.proto
@@ -15,6 +15,10 @@ message Status {
}
message PushServiceRegistration {
optional PushService service = 1;
+ optional GcmRegistration gcm_registration = 2;
+}
+message GcmRegistration {
+ optional string device_token = 1;
}
message DeviceIdentifiers {
optional int32 ulr_device_id = 1;
diff --git a/components/copresence/proto/push_message.proto b/components/copresence/proto/push_message.proto
new file mode 100644
index 0000000..239e885
--- /dev/null
+++ b/components/copresence/proto/push_message.proto
@@ -0,0 +1,20 @@
+syntax = "proto2";
+package copresence;
+option optimize_for = LITE_RUNTIME;
+import "data.proto";
+message PushMessage {
+ enum Type {
+ TYPE_UNKNOWN = 0;
+ SYNC_SETTINGS = 3;
+ OBTAIN_MAC = 5;
+ NOTIFY_MAC = 6;
+ TEST = 7;
+ REPORT = 8;
+ };
+ optional Type type = 1;
+ optional Report report = 9;
+}
+message Report {
+ repeated Directive directive = 1;
+ repeated SubscribedMessage subscribed_message = 2;
+}
diff --git a/components/copresence/public/copresence_delegate.h b/components/copresence/public/copresence_delegate.h
index b8a01bf..e138d66 100644
--- a/components/copresence/public/copresence_delegate.h
+++ b/components/copresence/public/copresence_delegate.h
@@ -10,6 +10,10 @@
#include "base/callback_forward.h"
+namespace gcm {
+class GCMDriver;
+}
+
namespace net {
class URLRequestContextGetter;
}
@@ -49,6 +53,10 @@ class CopresenceDelegate {
// Thw WhispernetClient must outlive the CopresenceManager.
virtual WhispernetClient* GetWhispernetClient() = 0;
+
+ // Clients may optionally provide a GCMDriver to receive messages from.
+ // If no driver is available, this can return null.
+ virtual gcm::GCMDriver* GetGCMDriver() = 0;
};
} // namespace copresence
diff --git a/components/copresence/rpc/rpc_handler.cc b/components/copresence/rpc/rpc_handler.cc
index 7c751ab..78bd958 100644
--- a/components/copresence/rpc/rpc_handler.cc
+++ b/components/copresence/rpc/rpc_handler.cc
@@ -21,6 +21,7 @@
#include "components/copresence/copresence_switches.h"
#include "components/copresence/handlers/directive_handler.h"
+#include "components/copresence/handlers/gcm_handler.h"
#include "components/copresence/proto/codes.pb.h"
#include "components/copresence/proto/data.pb.h"
#include "components/copresence/proto/rpcs.pb.h"
@@ -38,11 +39,15 @@ using google::protobuf::RepeatedPtrField;
const char RpcHandler::kReportRequestRpcName[] = "report";
-// Number of characters of suffix to log for auth tokens
-const int kTokenSuffix = 5;
-
namespace {
+const int kTokenLoggingSuffix = 5;
+const int kInvalidTokenExpiryTimeMs = 10 * 60 * 1000; // 10 minutes.
+const int kMaxInvalidTokens = 10000;
+const char kRegisterDeviceRpcName[] = "registerdevice";
+const char kDefaultCopresenceServer[] =
+ "https://www.googleapis.com/copresence/v2/copresence";
+
// UrlSafe is defined as:
// '/' represented by a '_' and '+' represented by a '-'
// TODO(rkc): Move this to the wrapper.
@@ -52,11 +57,6 @@ std::string ToUrlSafe(std::string token) {
return token;
}
-const int kInvalidTokenExpiryTimeMs = 10 * 60 * 1000; // 10 minutes.
-const int kMaxInvalidTokens = 10000;
-const char kRegisterDeviceRpcName[] = "registerdevice";
-const char kDefaultCopresenceServer[] =
- "https://www.googleapis.com/copresence/v2/copresence";
// Logging
@@ -94,6 +94,16 @@ bool ReportErrorLogged(const ReportResponse& response) {
return result;
}
+const std::string LoggingStrForToken(const std::string& auth_token) {
+ if (auth_token.empty())
+ return "anonymous";
+
+ std::string token_suffix = auth_token.substr(
+ auth_token.length() - kTokenLoggingSuffix, kTokenLoggingSuffix);
+ return base::StringPrintf("token ...%s", token_suffix.c_str());
+}
+
+
// Request construction
// TODO(ckehoe): Move these into a separate file?
@@ -149,14 +159,6 @@ void AddTokenToRequest(const AudioToken& token, ReportRequest* request) {
signals->set_observed_time_millis(base::Time::Now().ToJsTime());
}
-const std::string LoggingStrForToken(const std::string& auth_token) {
- std::string token_str = auth_token.empty() ? "anonymous" :
- base::StringPrintf("token ...%s",
- auth_token.substr(auth_token.length() - kTokenSuffix,
- kTokenSuffix).c_str());
- return token_str;
-}
-
} // namespace
@@ -164,25 +166,33 @@ const std::string LoggingStrForToken(const std::string& auth_token) {
RpcHandler::RpcHandler(CopresenceDelegate* delegate,
DirectiveHandler* directive_handler,
+ GCMHandler* gcm_handler,
const PostCallback& server_post_callback)
: delegate_(delegate),
directive_handler_(directive_handler),
+ gcm_handler_(gcm_handler),
server_post_callback_(server_post_callback),
invalid_audio_token_cache_(
base::TimeDelta::FromMilliseconds(kInvalidTokenExpiryTimeMs),
kMaxInvalidTokens) {
DCHECK(delegate_);
DCHECK(directive_handler_);
+ // |gcm_handler_| is optional.
if (server_post_callback_.is_null()) {
server_post_callback_ =
base::Bind(&RpcHandler::SendHttpPost, base::Unretained(this));
}
+
+ if (gcm_handler_) {
+ gcm_handler_->GetGcmId(
+ base::Bind(&RpcHandler::RegisterGcmId, base::Unretained(this)));
+ }
}
RpcHandler::~RpcHandler() {
- // Do not use |directive_handler_| here.
- // It will already have been destructed.
+ // Do not use |directive_handler_| or |gcm_handler_| here.
+ // They will already have been destructed.
for (HttpPost* post : pending_posts_)
delete post;
}
@@ -258,7 +268,8 @@ void RpcHandler::ReportTokens(const std::vector<AudioToken>& tokens) {
}
}
-// Private methods
+
+// Private functions.
RpcHandler::PendingRequest::PendingRequest(scoped_ptr<ReportRequest> report,
const std::string& app_id,
@@ -275,11 +286,17 @@ void RpcHandler::RegisterForToken(const std::string& auth_token) {
DVLOG(2) << "Sending " << LoggingStrForToken(auth_token)
<< " registration to server.";
- // Mark registration as in progress.
- device_id_by_auth_token_[auth_token] = "";
-
scoped_ptr<RegisterDeviceRequest> request(new RegisterDeviceRequest);
- request->mutable_push_service()->set_service(PUSH_SERVICE_NONE);
+
+ // Add a GCM ID for authenticated registration, if we have one.
+ if (auth_token.empty() || gcm_id_.empty()) {
+ request->mutable_push_service()->set_service(PUSH_SERVICE_NONE);
+ } else {
+ DVLOG(2) << "Registering GCM ID with " << LoggingStrForToken(auth_token);
+ request->mutable_push_service()->set_service(GCM);
+ request->mutable_push_service()->mutable_gcm_registration()
+ ->set_device_token(gcm_id_);
+ }
// Only identify as a Chrome device if we're in anonymous mode.
// Authenticated calls come from a "GAIA device".
@@ -288,18 +305,28 @@ void RpcHandler::RegisterForToken(const std::string& auth_token) {
request->mutable_device_identifiers()->mutable_registrant();
identity->set_type(CHROME);
identity->set_chrome_id(base::GenerateGUID());
+
+ // Since we're generating a new "Chrome ID" here,
+ // we need to make sure this isn't a duplicate registration.
+ DCHECK_EQ(0u, device_id_by_auth_token_.count(std::string()))
+ << "Attempted anonymous re-registration";
}
+ bool gcm_pending = !auth_token.empty() && gcm_handler_ && gcm_id_.empty();
SendServerRequest(
kRegisterDeviceRpcName,
- std::string(), // device ID
+ // This will have the side effect of populating an empty device ID
+ // for this auth token in the map. This is what we want,
+ // to mark registration as being in progress.
+ device_id_by_auth_token_[auth_token],
std::string(), // app ID
auth_token,
request.Pass(),
base::Bind(&RpcHandler::RegisterResponseHandler,
// On destruction, this request will be cancelled.
base::Unretained(this),
- auth_token));
+ auth_token,
+ gcm_pending));
}
void RpcHandler::ProcessQueuedRequests(const std::string& auth_token) {
@@ -343,8 +370,41 @@ void RpcHandler::SendReportRequest(scoped_ptr<ReportRequest> request,
StatusCallback());
}
+// Store a GCM ID and send it to the server if needed. The constructor passes
+// this callback to the GCMHandler to receive the ID whenever it's ready.
+// It may be returned immediately, if the ID is cached, or require a server
+// round-trip. This ID must then be passed along to the copresence server.
+// There are a few ways this can happen for each auth token:
+//
+// 1. The GCM ID is available when we first register, and is passed along
+// with the RegisterDeviceRequest.
+//
+// 2. The GCM ID becomes available after the RegisterDeviceRequest has
+// completed. Then the loop in this function will invoke RegisterForToken()
+// again to pass on the ID.
+//
+// 3. The GCM ID becomes available after the RegisterDeviceRequest is sent,
+// but before it completes. In this case, the gcm_pending flag is passed
+// through to the RegisterResponseHandler, which invokes RegisterForToken()
+// again to pass on the ID. The loop here must skip pending registrations,
+// as the device ID will be empty.
+//
+// TODO(ckehoe): Add tests for these scenarios.
+void RpcHandler::RegisterGcmId(const std::string& gcm_id) {
+ gcm_id_ = gcm_id;
+ if (!gcm_id.empty()) {
+ for (const auto& registration : device_id_by_auth_token_) {
+ const std::string& auth_token = registration.first;
+ const std::string& device_id = registration.second;
+ if (!auth_token.empty() && !device_id.empty())
+ RegisterForToken(auth_token);
+ }
+ }
+}
+
void RpcHandler::RegisterResponseHandler(
const std::string& auth_token,
+ bool gcm_pending,
HttpPost* completed_post,
int http_status_code,
const std::string& response_data) {
@@ -371,6 +431,10 @@ void RpcHandler::RegisterResponseHandler(
device_id_by_auth_token_[auth_token] = device_id;
DVLOG(2) << LoggingStrForToken(auth_token)
<< " device registration successful. Id: " << device_id;
+
+ // If we have a GCM ID now, and didn't before, pass it on to the server.
+ if (gcm_pending && !gcm_id_.empty())
+ RegisterForToken(auth_token);
}
// Send or fail requests on this auth token.
@@ -519,7 +583,8 @@ RequestHeader* RpcHandler::CreateRequestHeader(
CreateVersion(client_name, std::string()));
}
header->set_current_time_millis(base::Time::Now().ToJsTime());
- header->set_registered_device_id(device_id);
+ if (!device_id.empty())
+ header->set_registered_device_id(device_id);
DeviceFingerprint* fingerprint = new DeviceFingerprint;
fingerprint->set_platform_version(delegate_->GetPlatformVersionString());
diff --git a/components/copresence/rpc/rpc_handler.h b/components/copresence/rpc/rpc_handler.h
index e2e999f..555a8b1 100644
--- a/components/copresence/rpc/rpc_handler.h
+++ b/components/copresence/rpc/rpc_handler.h
@@ -22,6 +22,7 @@ namespace copresence {
struct AudioToken;
class CopresenceDelegate;
class DirectiveHandler;
+class GCMHandler;
class HttpPost;
class ReportRequest;
class RequestHeader;
@@ -65,6 +66,7 @@ class RpcHandler {
// |server_post_callback| should be set only by tests.
RpcHandler(CopresenceDelegate* delegate,
DirectiveHandler* directive_handler,
+ GCMHandler* gcm_handler,
const PostCallback& server_post_callback = PostCallback());
virtual ~RpcHandler();
@@ -107,8 +109,12 @@ class RpcHandler {
void SendReportRequest(scoped_ptr<ReportRequest> request,
const std::string& auth_token);
+ // Store a GCM ID and send it to the server if needed.
+ void RegisterGcmId(const std::string& gcm_id);
+
// Server call response handlers.
void RegisterResponseHandler(const std::string& auth_token,
+ bool gcm_pending,
HttpPost* completed_post,
int http_status_code,
const std::string& response_data);
@@ -152,8 +158,9 @@ class RpcHandler {
const PostCleanupCallback& callback);
// These belong to the caller.
- CopresenceDelegate* delegate_;
- DirectiveHandler* directive_handler_;
+ CopresenceDelegate* const delegate_;
+ DirectiveHandler* const directive_handler_;
+ GCMHandler* const gcm_handler_;
PostCallback server_post_callback_;
@@ -161,6 +168,7 @@ class RpcHandler {
TimedMap<std::string, bool> invalid_audio_token_cache_;
std::map<std::string, std::string> device_id_by_auth_token_;
std::set<HttpPost*> pending_posts_;
+ std::string gcm_id_;
DISALLOW_COPY_AND_ASSIGN(RpcHandler);
};
diff --git a/components/copresence/rpc/rpc_handler_unittest.cc b/components/copresence/rpc/rpc_handler_unittest.cc
index 7efe2f6..62fd56a 100644
--- a/components/copresence/rpc/rpc_handler_unittest.cc
+++ b/components/copresence/rpc/rpc_handler_unittest.cc
@@ -17,6 +17,7 @@
#include "components/copresence/proto/data.pb.h"
#include "components/copresence/proto/enums.pb.h"
#include "components/copresence/proto/rpcs.pb.h"
+#include "components/copresence/test/fake_directive_handler.h"
#include "components/copresence/test/stub_whispernet_client.h"
#include "net/http/http_status_code.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -43,45 +44,6 @@ void CreateSubscribedMessage(const std::vector<std::string>& subscription_ids,
}
}
-// TODO(ckehoe): Make DirectiveHandler an interface.
-class FakeDirectiveHandler final : public DirectiveHandler {
- public:
- FakeDirectiveHandler() : DirectiveHandler(nullptr) {}
-
- const std::vector<std::string>& added_directives() const {
- return added_directives_;
- }
-
- const std::vector<std::string>& removed_directives() const {
- return removed_directives_;
- }
-
- void Start(WhispernetClient* /* whispernet_client */,
- const TokensCallback& /* tokens_cb */) override {
- NOTREACHED();
- }
-
- void AddDirective(const Directive& directive) override {
- added_directives_.push_back(directive.subscription_id());
- }
-
- void RemoveDirectives(const std::string& op_id) override {
- removed_directives_.push_back(op_id);
- }
-
- const std::string GetCurrentAudioToken(AudioType type) const override {
- return type == AUDIBLE ? "current audible" : "current inaudible";
- }
-
- bool IsAudioTokenHeard(AudioType type) const override { return true; }
-
- private:
- std::vector<std::string> added_directives_;
- std::vector<std::string> removed_directives_;
-
- DISALLOW_COPY_AND_ASSIGN(FakeDirectiveHandler);
-};
-
} // namespace
class RpcHandlerTest : public testing::Test, public CopresenceDelegate {
@@ -90,6 +52,7 @@ class RpcHandlerTest : public testing::Test, public CopresenceDelegate {
: whispernet_client_(new StubWhispernetClient),
rpc_handler_(this,
&directive_handler_,
+ nullptr,
base::Bind(&RpcHandlerTest::CaptureHttpPost,
base::Unretained(this))),
status_(SUCCESS) {}
@@ -123,6 +86,11 @@ class RpcHandlerTest : public testing::Test, public CopresenceDelegate {
return whispernet_client_.get();
}
+ // TODO(ckehoe): Add GCM tests.
+ gcm::GCMDriver* GetGCMDriver() override {
+ return nullptr;
+ }
+
protected:
// Send test input to RpcHandler
@@ -140,7 +108,7 @@ class RpcHandlerTest : public testing::Test, public CopresenceDelegate {
std::string serialized_response;
response.SerializeToString(&serialized_response);
rpc_handler_.RegisterResponseHandler(
- auth_token, nullptr, net::HTTP_OK, serialized_response);
+ auth_token, false, nullptr, net::HTTP_OK, serialized_response);
}
void SendReport(scoped_ptr<ReportRequest> request,
diff --git a/components/copresence/test/fake_directive_handler.cc b/components/copresence/test/fake_directive_handler.cc
new file mode 100644
index 0000000..810b227
--- /dev/null
+++ b/components/copresence/test/fake_directive_handler.cc
@@ -0,0 +1,32 @@
+// 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/copresence/test/fake_directive_handler.h"
+
+#include "components/copresence/proto/data.pb.h"
+
+namespace copresence {
+
+FakeDirectiveHandler::FakeDirectiveHandler() : DirectiveHandler(nullptr) {}
+
+FakeDirectiveHandler::~FakeDirectiveHandler() {}
+
+void FakeDirectiveHandler::AddDirective(const Directive& directive) {
+ added_directives_.push_back(directive.subscription_id());
+}
+
+void FakeDirectiveHandler::RemoveDirectives(const std::string& op_id) {
+ removed_directives_.push_back(op_id);
+}
+
+const std::string FakeDirectiveHandler::GetCurrentAudioToken(AudioType type)
+ const {
+ return type == AUDIBLE ? "current audible" : "current inaudible";
+}
+
+bool FakeDirectiveHandler::IsAudioTokenHeard(AudioType type) const {
+ return true;
+}
+
+} // namespace copresence
diff --git a/components/copresence/test/fake_directive_handler.h b/components/copresence/test/fake_directive_handler.h
new file mode 100644
index 0000000..f7b591c
--- /dev/null
+++ b/components/copresence/test/fake_directive_handler.h
@@ -0,0 +1,49 @@
+// 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_COPRESENCE_TEST_FAKE_DIRECTIVE_HANDLER_H_
+#define COMPONENTS_COPRESENCE_TEST_FAKE_DIRECTIVE_HANDLER_H_
+
+#include <string>
+#include <vector>
+
+#include "components/copresence/handlers/directive_handler.h"
+
+namespace copresence {
+
+// TODO(ckehoe): Make DirectiveHandler an interface.
+class FakeDirectiveHandler final : public DirectiveHandler {
+ public:
+ FakeDirectiveHandler();
+ ~FakeDirectiveHandler() override;
+
+ const std::vector<std::string>& added_directives() const {
+ return added_directives_;
+ }
+
+ const std::vector<std::string>& removed_directives() const {
+ return removed_directives_;
+ }
+
+ void Start(WhispernetClient* /* whispernet_client */,
+ const TokensCallback& /* tokens_cb */) override {}
+
+ void AddDirective(const Directive& directive) override;
+
+ void RemoveDirectives(const std::string& op_id) override;
+
+ const std::string GetCurrentAudioToken(AudioType type) const override;
+
+ bool IsAudioTokenHeard(AudioType type) const override;
+
+ private:
+ std::vector<std::string> added_directives_;
+ std::vector<std::string> removed_directives_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeDirectiveHandler);
+};
+
+} // namespace copresence
+
+#endif // COMPONENTS_COPRESENCE_TEST_FAKE_DIRECTIVE_HANDLER_H_