| // 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 "base/bind.h" |
| #include "base/location.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/thread_task_runner_handle.h" |
| #include "components/gcm_driver/gcm_driver.h" |
| #include "components/invalidation/gcm_invalidation_bridge.h" |
| #include "components/signin/core/browser/profile_oauth2_token_service.h" |
| #include "components/signin/core/browser/signin_manager.h" |
| #include "google_apis/gaia/gaia_constants.h" |
| #include "google_apis/gaia/identity_provider.h" |
| |
| namespace invalidation { |
| namespace { |
| // For 3rd party developers SenderId should come from application dashboard when |
| // server side application is registered with Google. Android invalidations use |
| // legacy format where gmail account can be specificed. Below value is copied |
| // from Android. |
| const char kInvalidationsSenderId[] = "ipc.invalidation@gmail.com"; |
| // In Android world AppId is provided by operating system and should |
| // match package name and hash of application. In desktop world these values |
| // are arbitrary and not verified/enforced by registration service (yet). |
| const char kInvalidationsAppId[] = "com.google.chrome.invalidations"; |
| |
| // Cacheinvalidation specific gcm message keys. |
| const char kContentKey[] = "content"; |
| const char kEchoTokenKey[] = "echo-token"; |
| } // namespace |
| |
| // Core should be very simple class that implements GCMNetwrokChannelDelegate |
| // and passes all calls to GCMInvalidationBridge. All calls should be serialized |
| // through GCMInvalidationBridge to avoid race conditions. |
| class GCMInvalidationBridge::Core : public syncer::GCMNetworkChannelDelegate, |
| public base::NonThreadSafe { |
| public: |
| Core(base::WeakPtr<GCMInvalidationBridge> bridge, |
| scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner); |
| ~Core() override; |
| |
| // syncer::GCMNetworkChannelDelegate implementation. |
| void Initialize(ConnectionStateCallback callback) override; |
| void RequestToken(RequestTokenCallback callback) override; |
| void InvalidateToken(const std::string& token) override; |
| void Register(RegisterCallback callback) override; |
| void SetMessageReceiver(MessageCallback callback) override; |
| |
| void RequestTokenFinished(RequestTokenCallback callback, |
| const GoogleServiceAuthError& error, |
| const std::string& token); |
| |
| void RegisterFinished(RegisterCallback callback, |
| const std::string& registration_id, |
| gcm::GCMClient::Result result); |
| |
| void OnIncomingMessage(const std::string& message, |
| const std::string& echo_token); |
| |
| void OnConnectionStateChanged(bool online); |
| |
| private: |
| base::WeakPtr<GCMInvalidationBridge> bridge_; |
| scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner_; |
| |
| MessageCallback message_callback_; |
| ConnectionStateCallback connection_state_callback_; |
| |
| base::WeakPtrFactory<Core> weak_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Core); |
| }; |
| |
| GCMInvalidationBridge::Core::Core( |
| base::WeakPtr<GCMInvalidationBridge> bridge, |
| scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner) |
| : bridge_(bridge), |
| ui_thread_task_runner_(ui_thread_task_runner), |
| weak_factory_(this) { |
| // Core is created on UI thread but all calls happen on IO thread. |
| DetachFromThread(); |
| } |
| |
| GCMInvalidationBridge::Core::~Core() {} |
| |
| void GCMInvalidationBridge::Core::Initialize(ConnectionStateCallback callback) { |
| DCHECK(CalledOnValidThread()); |
| connection_state_callback_ = callback; |
| // Pass core WeapPtr and TaskRunner to GCMInvalidationBridge for it to be able |
| // to post back. |
| ui_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&GCMInvalidationBridge::CoreInitializationDone, |
| bridge_, |
| weak_factory_.GetWeakPtr(), |
| base::ThreadTaskRunnerHandle::Get())); |
| } |
| |
| void GCMInvalidationBridge::Core::RequestToken(RequestTokenCallback callback) { |
| DCHECK(CalledOnValidThread()); |
| ui_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&GCMInvalidationBridge::RequestToken, bridge_, callback)); |
| } |
| |
| void GCMInvalidationBridge::Core::InvalidateToken(const std::string& token) { |
| DCHECK(CalledOnValidThread()); |
| ui_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&GCMInvalidationBridge::InvalidateToken, bridge_, token)); |
| } |
| |
| void GCMInvalidationBridge::Core::Register(RegisterCallback callback) { |
| DCHECK(CalledOnValidThread()); |
| ui_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&GCMInvalidationBridge::Register, bridge_, callback)); |
| } |
| |
| void GCMInvalidationBridge::Core::SetMessageReceiver(MessageCallback callback) { |
| message_callback_ = callback; |
| ui_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&GCMInvalidationBridge::SubscribeForIncomingMessages, |
| bridge_)); |
| } |
| |
| void GCMInvalidationBridge::Core::RequestTokenFinished( |
| RequestTokenCallback callback, |
| const GoogleServiceAuthError& error, |
| const std::string& token) { |
| DCHECK(CalledOnValidThread()); |
| callback.Run(error, token); |
| } |
| |
| void GCMInvalidationBridge::Core::RegisterFinished( |
| RegisterCallback callback, |
| const std::string& registration_id, |
| gcm::GCMClient::Result result) { |
| DCHECK(CalledOnValidThread()); |
| callback.Run(registration_id, result); |
| } |
| |
| void GCMInvalidationBridge::Core::OnIncomingMessage( |
| const std::string& message, |
| const std::string& echo_token) { |
| DCHECK(!message_callback_.is_null()); |
| message_callback_.Run(message, echo_token); |
| } |
| |
| void GCMInvalidationBridge::Core::OnConnectionStateChanged(bool online) { |
| if (!connection_state_callback_.is_null()) { |
| connection_state_callback_.Run(online); |
| } |
| } |
| |
| GCMInvalidationBridge::GCMInvalidationBridge( |
| gcm::GCMDriver* gcm_driver, |
| IdentityProvider* identity_provider) |
| : OAuth2TokenService::Consumer("gcm_network_channel"), |
| gcm_driver_(gcm_driver), |
| identity_provider_(identity_provider), |
| subscribed_for_incoming_messages_(false), |
| weak_factory_(this) {} |
| |
| GCMInvalidationBridge::~GCMInvalidationBridge() { |
| if (subscribed_for_incoming_messages_) { |
| gcm_driver_->RemoveAppHandler(kInvalidationsAppId); |
| gcm_driver_->RemoveConnectionObserver(this); |
| } |
| } |
| |
| scoped_ptr<syncer::GCMNetworkChannelDelegate> |
| GCMInvalidationBridge::CreateDelegate() { |
| DCHECK(CalledOnValidThread()); |
| scoped_ptr<syncer::GCMNetworkChannelDelegate> core(new Core( |
| weak_factory_.GetWeakPtr(), base::ThreadTaskRunnerHandle::Get())); |
| return core.Pass(); |
| } |
| |
| void GCMInvalidationBridge::CoreInitializationDone( |
| base::WeakPtr<Core> core, |
| scoped_refptr<base::SingleThreadTaskRunner> core_thread_task_runner) { |
| DCHECK(CalledOnValidThread()); |
| core_ = core; |
| core_thread_task_runner_ = core_thread_task_runner; |
| } |
| |
| void GCMInvalidationBridge::RequestToken( |
| syncer::GCMNetworkChannelDelegate::RequestTokenCallback callback) { |
| DCHECK(CalledOnValidThread()); |
| if (access_token_request_ != NULL) { |
| // Report previous request as cancelled. |
| GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED); |
| std::string access_token; |
| core_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&GCMInvalidationBridge::Core::RequestTokenFinished, |
| core_, |
| request_token_callback_, |
| error, |
| access_token)); |
| } |
| request_token_callback_ = callback; |
| OAuth2TokenService::ScopeSet scopes; |
| scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope); |
| access_token_request_ = identity_provider_->GetTokenService()->StartRequest( |
| identity_provider_->GetActiveAccountId(), scopes, this); |
| } |
| |
| void GCMInvalidationBridge::OnGetTokenSuccess( |
| const OAuth2TokenService::Request* request, |
| const std::string& access_token, |
| const base::Time& expiration_time) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(access_token_request_, request); |
| core_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&GCMInvalidationBridge::Core::RequestTokenFinished, |
| core_, |
| request_token_callback_, |
| GoogleServiceAuthError::AuthErrorNone(), |
| access_token)); |
| request_token_callback_.Reset(); |
| access_token_request_.reset(); |
| } |
| |
| void GCMInvalidationBridge::OnGetTokenFailure( |
| const OAuth2TokenService::Request* request, |
| const GoogleServiceAuthError& error) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(access_token_request_, request); |
| core_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&GCMInvalidationBridge::Core::RequestTokenFinished, |
| core_, |
| request_token_callback_, |
| error, |
| std::string())); |
| request_token_callback_.Reset(); |
| access_token_request_.reset(); |
| } |
| |
| void GCMInvalidationBridge::InvalidateToken(const std::string& token) { |
| DCHECK(CalledOnValidThread()); |
| OAuth2TokenService::ScopeSet scopes; |
| scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope); |
| identity_provider_->GetTokenService()->InvalidateToken( |
| identity_provider_->GetActiveAccountId(), scopes, token); |
| } |
| |
| void GCMInvalidationBridge::Register( |
| syncer::GCMNetworkChannelDelegate::RegisterCallback callback) { |
| DCHECK(CalledOnValidThread()); |
| // No-op if GCMClient is disabled. |
| if (gcm_driver_ == NULL) |
| return; |
| |
| std::vector<std::string> sender_ids; |
| sender_ids.push_back(kInvalidationsSenderId); |
| gcm_driver_->Register(kInvalidationsAppId, |
| sender_ids, |
| base::Bind(&GCMInvalidationBridge::RegisterFinished, |
| weak_factory_.GetWeakPtr(), |
| callback)); |
| } |
| |
| void GCMInvalidationBridge::RegisterFinished( |
| syncer::GCMNetworkChannelDelegate::RegisterCallback callback, |
| const std::string& registration_id, |
| gcm::GCMClient::Result result) { |
| DCHECK(CalledOnValidThread()); |
| core_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&GCMInvalidationBridge::Core::RegisterFinished, |
| core_, |
| callback, |
| registration_id, |
| result)); |
| } |
| |
| void GCMInvalidationBridge::Unregister() { |
| DCHECK(CalledOnValidThread()); |
| // No-op if GCMClient is disabled. |
| if (gcm_driver_ == NULL) |
| return; |
| |
| gcm_driver_->Unregister( |
| kInvalidationsAppId, |
| base::Bind(&GCMInvalidationBridge::UnregisterFinishedNoOp)); |
| } |
| |
| // static |
| void GCMInvalidationBridge::UnregisterFinishedNoOp( |
| gcm::GCMClient::Result result) { |
| // No-op. |
| } |
| |
| void GCMInvalidationBridge::SubscribeForIncomingMessages() { |
| // No-op if GCMClient is disabled. |
| if (gcm_driver_ == NULL) |
| return; |
| |
| DCHECK(!subscribed_for_incoming_messages_); |
| gcm_driver_->AddAppHandler(kInvalidationsAppId, this); |
| gcm_driver_->AddConnectionObserver(this); |
| core_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&GCMInvalidationBridge::Core::OnConnectionStateChanged, |
| core_, |
| gcm_driver_->IsConnected())); |
| |
| subscribed_for_incoming_messages_ = true; |
| } |
| |
| void GCMInvalidationBridge::ShutdownHandler() { |
| // Nothing to do. |
| } |
| |
| void GCMInvalidationBridge::OnMessage( |
| const std::string& app_id, |
| const gcm::GCMClient::IncomingMessage& message) { |
| gcm::GCMClient::MessageData::const_iterator it; |
| std::string content; |
| std::string echo_token; |
| it = message.data.find(kContentKey); |
| if (it != message.data.end()) |
| content = it->second; |
| it = message.data.find(kEchoTokenKey); |
| if (it != message.data.end()) |
| echo_token = it->second; |
| |
| core_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&GCMInvalidationBridge::Core::OnIncomingMessage, |
| core_, |
| content, |
| echo_token)); |
| } |
| |
| void GCMInvalidationBridge::OnMessagesDeleted(const std::string& app_id) { |
| // Cacheinvalidation doesn't use long lived non-collapsable messages with GCM. |
| // Android implementation of cacheinvalidation doesn't handle MessagesDeleted |
| // callback so this should be no-op in desktop version as well. |
| } |
| |
| void GCMInvalidationBridge::OnSendError( |
| const std::string& app_id, |
| const gcm::GCMClient::SendErrorDetails& send_error_details) { |
| // cacheinvalidation doesn't send messages over GCM. |
| NOTREACHED(); |
| } |
| |
| void GCMInvalidationBridge::OnSendAcknowledged( |
| const std::string& app_id, |
| const std::string& message_id) { |
| // cacheinvalidation doesn't send messages over GCM. |
| NOTREACHED(); |
| } |
| |
| void GCMInvalidationBridge::OnConnected(const net::IPEndPoint& ip_endpoint) { |
| core_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind( |
| &GCMInvalidationBridge::Core::OnConnectionStateChanged, core_, true)); |
| } |
| |
| void GCMInvalidationBridge::OnDisconnected() { |
| core_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&GCMInvalidationBridge::Core::OnConnectionStateChanged, |
| core_, |
| false)); |
| } |
| |
| } // namespace invalidation |