Explicitly add python-numpy dependency to install-build-deps.
[chromium-blink-merge.git] / components / gcm_driver / gcm_client_impl.cc
blob4a68dc0766f61c76c492fc5eeb1976109f1ef0f4
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/gcm_driver/gcm_client_impl.h"
7 #include "base/bind.h"
8 #include "base/files/file_path.h"
9 #include "base/logging.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/metrics/histogram.h"
13 #include "base/sequenced_task_runner.h"
14 #include "base/stl_util.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/time/default_clock.h"
18 #include "components/gcm_driver/gcm_backoff_policy.h"
19 #include "components/timers/alarm_timer.h"
20 #include "google_apis/gcm/base/encryptor.h"
21 #include "google_apis/gcm/base/mcs_message.h"
22 #include "google_apis/gcm/base/mcs_util.h"
23 #include "google_apis/gcm/engine/checkin_request.h"
24 #include "google_apis/gcm/engine/connection_factory_impl.h"
25 #include "google_apis/gcm/engine/gcm_store_impl.h"
26 #include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
27 #include "google_apis/gcm/protocol/checkin.pb.h"
28 #include "google_apis/gcm/protocol/mcs.pb.h"
29 #include "net/http/http_network_session.h"
30 #include "net/http/http_transaction_factory.h"
31 #include "net/url_request/url_request_context.h"
32 #include "url/gurl.h"
34 namespace gcm {
36 namespace {
38 // Indicates a message type of the received message.
39 enum MessageType {
40 UNKNOWN, // Undetermined type.
41 DATA_MESSAGE, // Regular data message.
42 DELETED_MESSAGES, // Messages were deleted on the server.
43 SEND_ERROR, // Error sending a message.
46 enum OutgoingMessageTTLCategory {
47 TTL_ZERO,
48 TTL_LESS_THAN_OR_EQUAL_TO_ONE_MINUTE,
49 TTL_LESS_THAN_OR_EQUAL_TO_ONE_HOUR,
50 TTL_LESS_THAN_OR_EQUAL_TO_ONE_DAY,
51 TTL_LESS_THAN_OR_EQUAL_TO_ONE_WEEK,
52 TTL_MORE_THAN_ONE_WEEK,
53 TTL_MAXIMUM,
54 // NOTE: always keep this entry at the end. Add new TTL category only
55 // immediately above this line. Make sure to update the corresponding
56 // histogram enum accordingly.
57 TTL_CATEGORY_COUNT
60 const int kMaxRegistrationRetries = 5;
61 const char kMessageTypeDataMessage[] = "gcm";
62 const char kMessageTypeDeletedMessagesKey[] = "deleted_messages";
63 const char kMessageTypeKey[] = "message_type";
64 const char kMessageTypeSendErrorKey[] = "send_error";
65 const char kSendErrorMessageIdKey[] = "google.message_id";
66 const char kSendMessageFromValue[] = "gcm@chrome.com";
67 const int64 kDefaultUserSerialNumber = 0LL;
69 GCMClient::Result ToGCMClientResult(MCSClient::MessageSendStatus status) {
70 switch (status) {
71 case MCSClient::QUEUED:
72 return GCMClient::SUCCESS;
73 case MCSClient::QUEUE_SIZE_LIMIT_REACHED:
74 return GCMClient::NETWORK_ERROR;
75 case MCSClient::APP_QUEUE_SIZE_LIMIT_REACHED:
76 return GCMClient::NETWORK_ERROR;
77 case MCSClient::MESSAGE_TOO_LARGE:
78 return GCMClient::INVALID_PARAMETER;
79 case MCSClient::NO_CONNECTION_ON_ZERO_TTL:
80 return GCMClient::NETWORK_ERROR;
81 case MCSClient::TTL_EXCEEDED:
82 return GCMClient::NETWORK_ERROR;
83 case MCSClient::SENT:
84 default:
85 NOTREACHED();
86 break;
88 return GCMClientImpl::UNKNOWN_ERROR;
91 void ToCheckinProtoVersion(
92 const GCMClient::ChromeBuildInfo& chrome_build_info,
93 checkin_proto::ChromeBuildProto* android_build_info) {
94 checkin_proto::ChromeBuildProto_Platform platform;
95 switch (chrome_build_info.platform) {
96 case GCMClient::PLATFORM_WIN:
97 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_WIN;
98 break;
99 case GCMClient::PLATFORM_MAC:
100 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_MAC;
101 break;
102 case GCMClient::PLATFORM_LINUX:
103 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX;
104 break;
105 case GCMClient::PLATFORM_IOS:
106 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_IOS;
107 break;
108 case GCMClient::PLATFORM_ANDROID:
109 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_ANDROID;
110 break;
111 case GCMClient::PLATFORM_CROS:
112 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_CROS;
113 break;
114 case GCMClient::PLATFORM_UNKNOWN:
115 // For unknown platform, return as LINUX.
116 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX;
117 break;
118 default:
119 NOTREACHED();
120 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX;
121 break;
123 android_build_info->set_platform(platform);
125 checkin_proto::ChromeBuildProto_Channel channel;
126 switch (chrome_build_info.channel) {
127 case GCMClient::CHANNEL_STABLE:
128 channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_STABLE;
129 break;
130 case GCMClient::CHANNEL_BETA:
131 channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_BETA;
132 break;
133 case GCMClient::CHANNEL_DEV:
134 channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_DEV;
135 break;
136 case GCMClient::CHANNEL_CANARY:
137 channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_CANARY;
138 break;
139 case GCMClient::CHANNEL_UNKNOWN:
140 channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_UNKNOWN;
141 break;
142 default:
143 NOTREACHED();
144 channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_UNKNOWN;
145 break;
147 android_build_info->set_channel(channel);
149 android_build_info->set_chrome_version(chrome_build_info.version);
152 MessageType DecodeMessageType(const std::string& value) {
153 if (kMessageTypeDeletedMessagesKey == value)
154 return DELETED_MESSAGES;
155 if (kMessageTypeSendErrorKey == value)
156 return SEND_ERROR;
157 if (kMessageTypeDataMessage == value)
158 return DATA_MESSAGE;
159 return UNKNOWN;
162 void RecordOutgoingMessageToUMA(
163 const gcm::GCMClient::OutgoingMessage& message) {
164 OutgoingMessageTTLCategory ttl_category;
165 if (message.time_to_live == 0)
166 ttl_category = TTL_ZERO;
167 else if (message.time_to_live <= 60 )
168 ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_MINUTE;
169 else if (message.time_to_live <= 60 * 60)
170 ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_HOUR;
171 else if (message.time_to_live <= 24 * 60 * 60)
172 ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_DAY;
173 else
174 ttl_category = TTL_MAXIMUM;
176 UMA_HISTOGRAM_ENUMERATION("GCM.OutgoingMessageTTL",
177 ttl_category,
178 TTL_CATEGORY_COUNT);
181 } // namespace
183 GCMInternalsBuilder::GCMInternalsBuilder() {}
184 GCMInternalsBuilder::~GCMInternalsBuilder() {}
186 scoped_ptr<base::Clock> GCMInternalsBuilder::BuildClock() {
187 return make_scoped_ptr<base::Clock>(new base::DefaultClock());
190 scoped_ptr<MCSClient> GCMInternalsBuilder::BuildMCSClient(
191 const std::string& version,
192 base::Clock* clock,
193 ConnectionFactory* connection_factory,
194 GCMStore* gcm_store,
195 GCMStatsRecorder* recorder) {
196 #if defined(OS_CHROMEOS)
197 return scoped_ptr<MCSClient>(new MCSClient(
198 version, clock, connection_factory, gcm_store, recorder,
199 make_scoped_ptr(new timers::AlarmTimer(true, /* retain user task */
200 false /* non-repeating */))));
201 #else
202 return scoped_ptr<MCSClient>(new MCSClient(
203 version, clock, connection_factory, gcm_store, recorder,
204 make_scoped_ptr(new base::Timer(true, /* retain user task */
205 false /* non-repeating */))));
206 #endif // defined(OS_CHROMEOS)
209 scoped_ptr<ConnectionFactory> GCMInternalsBuilder::BuildConnectionFactory(
210 const std::vector<GURL>& endpoints,
211 const net::BackoffEntry::Policy& backoff_policy,
212 const scoped_refptr<net::HttpNetworkSession>& gcm_network_session,
213 const scoped_refptr<net::HttpNetworkSession>& http_network_session,
214 net::NetLog* net_log,
215 GCMStatsRecorder* recorder) {
216 return make_scoped_ptr<ConnectionFactory>(
217 new ConnectionFactoryImpl(endpoints,
218 backoff_policy,
219 gcm_network_session,
220 http_network_session,
221 net_log,
222 recorder));
225 GCMClientImpl::CheckinInfo::CheckinInfo()
226 : android_id(0), secret(0), accounts_set(false) {
229 GCMClientImpl::CheckinInfo::~CheckinInfo() {
232 void GCMClientImpl::CheckinInfo::SnapshotCheckinAccounts() {
233 last_checkin_accounts.clear();
234 for (std::map<std::string, std::string>::iterator iter =
235 account_tokens.begin();
236 iter != account_tokens.end();
237 ++iter) {
238 last_checkin_accounts.insert(iter->first);
242 void GCMClientImpl::CheckinInfo::Reset() {
243 android_id = 0;
244 secret = 0;
245 accounts_set = false;
246 account_tokens.clear();
247 last_checkin_accounts.clear();
250 GCMClientImpl::GCMClientImpl(scoped_ptr<GCMInternalsBuilder> internals_builder)
251 : internals_builder_(internals_builder.Pass()),
252 state_(UNINITIALIZED),
253 delegate_(NULL),
254 clock_(internals_builder_->BuildClock()),
255 url_request_context_getter_(NULL),
256 pending_registration_requests_deleter_(&pending_registration_requests_),
257 pending_unregistration_requests_deleter_(
258 &pending_unregistration_requests_),
259 periodic_checkin_ptr_factory_(this),
260 weak_ptr_factory_(this) {
263 GCMClientImpl::~GCMClientImpl() {
266 void GCMClientImpl::Initialize(
267 const ChromeBuildInfo& chrome_build_info,
268 const base::FilePath& path,
269 const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner,
270 const scoped_refptr<net::URLRequestContextGetter>&
271 url_request_context_getter,
272 scoped_ptr<Encryptor> encryptor,
273 GCMClient::Delegate* delegate) {
274 DCHECK_EQ(UNINITIALIZED, state_);
275 DCHECK(url_request_context_getter.get());
276 DCHECK(delegate);
278 url_request_context_getter_ = url_request_context_getter;
279 const net::HttpNetworkSession::Params* network_session_params =
280 url_request_context_getter_->GetURLRequestContext()->
281 GetNetworkSessionParams();
282 DCHECK(network_session_params);
283 network_session_ = new net::HttpNetworkSession(*network_session_params);
285 chrome_build_info_ = chrome_build_info;
287 gcm_store_.reset(
288 new GCMStoreImpl(path, blocking_task_runner, encryptor.Pass()));
290 delegate_ = delegate;
292 recorder_.SetDelegate(this);
294 state_ = INITIALIZED;
297 void GCMClientImpl::Start() {
298 DCHECK_EQ(INITIALIZED, state_);
300 // Once the loading is completed, the check-in will be initiated.
301 gcm_store_->Load(base::Bind(&GCMClientImpl::OnLoadCompleted,
302 weak_ptr_factory_.GetWeakPtr()));
303 state_ = LOADING;
306 void GCMClientImpl::OnLoadCompleted(scoped_ptr<GCMStore::LoadResult> result) {
307 DCHECK_EQ(LOADING, state_);
309 if (!result->success) {
310 ResetState();
311 return;
314 registrations_ = result->registrations;
315 device_checkin_info_.android_id = result->device_android_id;
316 device_checkin_info_.secret = result->device_security_token;
317 device_checkin_info_.last_checkin_accounts = result->last_checkin_accounts;
318 // A case where there were previously no accounts reported with checkin is
319 // considered to be the same as when the list of accounts is empty. It enables
320 // scheduling a periodic checkin for devices with no signed in users
321 // immediately after restart, while keeping |accounts_set == false| delays the
322 // checkin until the list of accounts is set explicitly.
323 if (result->last_checkin_accounts.size() == 0)
324 device_checkin_info_.accounts_set = true;
325 last_checkin_time_ = result->last_checkin_time;
326 gservices_settings_.UpdateFromLoadResult(*result);
327 // Taking over the value of account_mappings before passing the ownership of
328 // load result to InitializeMCSClient.
329 std::vector<AccountMapping> account_mappings;
330 account_mappings.swap(result->account_mappings);
331 base::Time last_token_fetch_time = result->last_token_fetch_time;
333 InitializeMCSClient(result.Pass());
335 if (device_checkin_info_.IsValid()) {
336 SchedulePeriodicCheckin();
337 OnReady(account_mappings, last_token_fetch_time);
338 return;
341 state_ = INITIAL_DEVICE_CHECKIN;
342 device_checkin_info_.Reset();
343 StartCheckin();
346 void GCMClientImpl::InitializeMCSClient(
347 scoped_ptr<GCMStore::LoadResult> result) {
348 std::vector<GURL> endpoints;
349 endpoints.push_back(gservices_settings_.GetMCSMainEndpoint());
350 endpoints.push_back(gservices_settings_.GetMCSFallbackEndpoint());
351 connection_factory_ = internals_builder_->BuildConnectionFactory(
352 endpoints,
353 GetGCMBackoffPolicy(),
354 network_session_,
355 url_request_context_getter_->GetURLRequestContext()
356 ->http_transaction_factory()
357 ->GetSession(),
358 net_log_.net_log(),
359 &recorder_);
360 connection_factory_->SetConnectionListener(this);
361 mcs_client_ = internals_builder_->BuildMCSClient(
362 chrome_build_info_.version,
363 clock_.get(),
364 connection_factory_.get(),
365 gcm_store_.get(),
366 &recorder_).Pass();
368 mcs_client_->Initialize(
369 base::Bind(&GCMClientImpl::OnMCSError, weak_ptr_factory_.GetWeakPtr()),
370 base::Bind(&GCMClientImpl::OnMessageReceivedFromMCS,
371 weak_ptr_factory_.GetWeakPtr()),
372 base::Bind(&GCMClientImpl::OnMessageSentToMCS,
373 weak_ptr_factory_.GetWeakPtr()),
374 result.Pass());
377 void GCMClientImpl::OnFirstTimeDeviceCheckinCompleted(
378 const CheckinInfo& checkin_info) {
379 DCHECK(!device_checkin_info_.IsValid());
381 device_checkin_info_.android_id = checkin_info.android_id;
382 device_checkin_info_.secret = checkin_info.secret;
383 // If accounts were not set by now, we can consider them set (to empty list)
384 // to make sure periodic checkins get scheduled after initial checkin.
385 device_checkin_info_.accounts_set = true;
386 gcm_store_->SetDeviceCredentials(
387 checkin_info.android_id, checkin_info.secret,
388 base::Bind(&GCMClientImpl::SetDeviceCredentialsCallback,
389 weak_ptr_factory_.GetWeakPtr()));
391 OnReady(std::vector<AccountMapping>(), base::Time());
394 void GCMClientImpl::OnReady(const std::vector<AccountMapping>& account_mappings,
395 const base::Time& last_token_fetch_time) {
396 state_ = READY;
397 StartMCSLogin();
399 delegate_->OnGCMReady(account_mappings, last_token_fetch_time);
402 void GCMClientImpl::StartMCSLogin() {
403 DCHECK_EQ(READY, state_);
404 DCHECK(device_checkin_info_.IsValid());
405 mcs_client_->Login(device_checkin_info_.android_id,
406 device_checkin_info_.secret);
409 void GCMClientImpl::ResetState() {
410 state_ = UNINITIALIZED;
411 // TODO(fgorski): reset all of the necessart objects and start over.
414 void GCMClientImpl::SetAccountTokens(
415 const std::vector<AccountTokenInfo>& account_tokens) {
416 device_checkin_info_.account_tokens.clear();
417 for (std::vector<AccountTokenInfo>::const_iterator iter =
418 account_tokens.begin();
419 iter != account_tokens.end();
420 ++iter) {
421 device_checkin_info_.account_tokens[iter->email] = iter->access_token;
424 bool accounts_set_before = device_checkin_info_.accounts_set;
425 device_checkin_info_.accounts_set = true;
427 DVLOG(1) << "Set account called with: " << account_tokens.size()
428 << " accounts.";
430 if (state_ != READY && state_ != INITIAL_DEVICE_CHECKIN)
431 return;
433 bool account_removed = false;
434 for (std::set<std::string>::iterator iter =
435 device_checkin_info_.last_checkin_accounts.begin();
436 iter != device_checkin_info_.last_checkin_accounts.end();
437 ++iter) {
438 if (device_checkin_info_.account_tokens.find(*iter) ==
439 device_checkin_info_.account_tokens.end()) {
440 account_removed = true;
444 // Checkin will be forced when any of the accounts was removed during the
445 // current Chrome session or if there has been an account removed between the
446 // restarts of Chrome. If there is a checkin in progress, it will be canceled.
447 // We only force checkin when user signs out. When there is a new account
448 // signed in, the periodic checkin will take care of adding the association in
449 // reasonable time.
450 if (account_removed) {
451 DVLOG(1) << "Detected that account has been removed. Forcing checkin.";
452 checkin_request_.reset();
453 StartCheckin();
454 } else if (!accounts_set_before) {
455 SchedulePeriodicCheckin();
456 DVLOG(1) << "Accounts set for the first time. Scheduled periodic checkin.";
460 void GCMClientImpl::UpdateAccountMapping(
461 const AccountMapping& account_mapping) {
462 gcm_store_->AddAccountMapping(account_mapping,
463 base::Bind(&GCMClientImpl::DefaultStoreCallback,
464 weak_ptr_factory_.GetWeakPtr()));
467 void GCMClientImpl::RemoveAccountMapping(const std::string& account_id) {
468 gcm_store_->RemoveAccountMapping(
469 account_id,
470 base::Bind(&GCMClientImpl::DefaultStoreCallback,
471 weak_ptr_factory_.GetWeakPtr()));
474 void GCMClientImpl::SetLastTokenFetchTime(const base::Time& time) {
475 gcm_store_->SetLastTokenFetchTime(
476 time,
477 base::Bind(&GCMClientImpl::IgnoreWriteResultCallback,
478 weak_ptr_factory_.GetWeakPtr()));
481 void GCMClientImpl::StartCheckin() {
482 // Make sure no checkin is in progress.
483 if (checkin_request_.get())
484 return;
486 checkin_proto::ChromeBuildProto chrome_build_proto;
487 ToCheckinProtoVersion(chrome_build_info_, &chrome_build_proto);
488 CheckinRequest::RequestInfo request_info(device_checkin_info_.android_id,
489 device_checkin_info_.secret,
490 device_checkin_info_.account_tokens,
491 gservices_settings_.digest(),
492 chrome_build_proto);
493 checkin_request_.reset(
494 new CheckinRequest(gservices_settings_.GetCheckinURL(),
495 request_info,
496 GetGCMBackoffPolicy(),
497 base::Bind(&GCMClientImpl::OnCheckinCompleted,
498 weak_ptr_factory_.GetWeakPtr()),
499 url_request_context_getter_.get(),
500 &recorder_));
501 // Taking a snapshot of the accounts count here, as there might be an asynch
502 // update of the account tokens while checkin is in progress.
503 device_checkin_info_.SnapshotCheckinAccounts();
504 checkin_request_->Start();
507 void GCMClientImpl::OnCheckinCompleted(
508 const checkin_proto::AndroidCheckinResponse& checkin_response) {
509 checkin_request_.reset();
511 if (!checkin_response.has_android_id() ||
512 !checkin_response.has_security_token()) {
513 // TODO(fgorski): I don't think a retry here will help, we should probably
514 // start over. By checking in with (0, 0).
515 return;
518 CheckinInfo checkin_info;
519 checkin_info.android_id = checkin_response.android_id();
520 checkin_info.secret = checkin_response.security_token();
522 if (state_ == INITIAL_DEVICE_CHECKIN) {
523 OnFirstTimeDeviceCheckinCompleted(checkin_info);
524 } else {
525 // checkin_info is not expected to change after a periodic checkin as it
526 // would invalidate the registratoin IDs.
527 DCHECK_EQ(READY, state_);
528 DCHECK_EQ(device_checkin_info_.android_id, checkin_info.android_id);
529 DCHECK_EQ(device_checkin_info_.secret, checkin_info.secret);
532 if (device_checkin_info_.IsValid()) {
533 // First update G-services settings, as something might have changed.
534 if (gservices_settings_.UpdateFromCheckinResponse(checkin_response)) {
535 gcm_store_->SetGServicesSettings(
536 gservices_settings_.settings_map(),
537 gservices_settings_.digest(),
538 base::Bind(&GCMClientImpl::SetGServicesSettingsCallback,
539 weak_ptr_factory_.GetWeakPtr()));
542 last_checkin_time_ = clock_->Now();
543 gcm_store_->SetLastCheckinInfo(
544 last_checkin_time_,
545 device_checkin_info_.last_checkin_accounts,
546 base::Bind(&GCMClientImpl::SetLastCheckinInfoCallback,
547 weak_ptr_factory_.GetWeakPtr()));
548 SchedulePeriodicCheckin();
552 void GCMClientImpl::SetGServicesSettingsCallback(bool success) {
553 DCHECK(success);
556 void GCMClientImpl::SchedulePeriodicCheckin() {
557 // Make sure no checkin is in progress.
558 if (checkin_request_.get() || !device_checkin_info_.accounts_set)
559 return;
561 // There should be only one periodic checkin pending at a time. Removing
562 // pending periodic checkin to schedule a new one.
563 periodic_checkin_ptr_factory_.InvalidateWeakPtrs();
565 base::TimeDelta time_to_next_checkin = GetTimeToNextCheckin();
566 if (time_to_next_checkin < base::TimeDelta())
567 time_to_next_checkin = base::TimeDelta();
569 base::MessageLoop::current()->PostDelayedTask(
570 FROM_HERE,
571 base::Bind(&GCMClientImpl::StartCheckin,
572 periodic_checkin_ptr_factory_.GetWeakPtr()),
573 time_to_next_checkin);
576 base::TimeDelta GCMClientImpl::GetTimeToNextCheckin() const {
577 return last_checkin_time_ + gservices_settings_.GetCheckinInterval() -
578 clock_->Now();
581 void GCMClientImpl::SetLastCheckinInfoCallback(bool success) {
582 // TODO(fgorski): This is one of the signals that store needs a rebuild.
583 DCHECK(success);
586 void GCMClientImpl::SetDeviceCredentialsCallback(bool success) {
587 // TODO(fgorski): This is one of the signals that store needs a rebuild.
588 DCHECK(success);
591 void GCMClientImpl::UpdateRegistrationCallback(bool success) {
592 // TODO(fgorski): This is one of the signals that store needs a rebuild.
593 DCHECK(success);
596 void GCMClientImpl::DefaultStoreCallback(bool success) {
597 DCHECK(success);
600 void GCMClientImpl::IgnoreWriteResultCallback(bool success) {
601 // TODO(fgorski): Ignoring the write result for now to make sure
602 // sync_intergration_tests are not broken.
605 void GCMClientImpl::Stop() {
606 DVLOG(1) << "Stopping the GCM Client";
607 weak_ptr_factory_.InvalidateWeakPtrs();
608 device_checkin_info_.Reset();
609 connection_factory_.reset();
610 delegate_->OnDisconnected();
611 mcs_client_.reset();
612 checkin_request_.reset();
613 // Delete all of the pending registration requests, whithout telling the
614 // consumers.
615 // TODO(fgorski): Perhaps we should make a distinction between a Stop and a
616 // Shutdown.
617 STLDeleteValues(&pending_registration_requests_);
618 state_ = INITIALIZED;
619 gcm_store_->Close();
622 void GCMClientImpl::CheckOut() {
623 Stop();
624 gcm_store_->Destroy(base::Bind(&GCMClientImpl::OnGCMStoreDestroyed,
625 weak_ptr_factory_.GetWeakPtr()));
628 void GCMClientImpl::Register(const std::string& app_id,
629 const std::vector<std::string>& sender_ids) {
630 DCHECK_EQ(state_, READY);
632 // If the same sender ids is provided, return the cached registration ID
633 // directly.
634 RegistrationInfoMap::const_iterator registrations_iter =
635 registrations_.find(app_id);
636 if (registrations_iter != registrations_.end() &&
637 registrations_iter->second->sender_ids == sender_ids) {
638 delegate_->OnRegisterFinished(
639 app_id, registrations_iter->second->registration_id, SUCCESS);
640 return;
643 RegistrationRequest::RequestInfo request_info(
644 device_checkin_info_.android_id,
645 device_checkin_info_.secret,
646 app_id,
647 sender_ids);
648 DCHECK_EQ(0u, pending_registration_requests_.count(app_id));
650 RegistrationRequest* registration_request =
651 new RegistrationRequest(gservices_settings_.GetRegistrationURL(),
652 request_info,
653 GetGCMBackoffPolicy(),
654 base::Bind(&GCMClientImpl::OnRegisterCompleted,
655 weak_ptr_factory_.GetWeakPtr(),
656 app_id,
657 sender_ids),
658 kMaxRegistrationRetries,
659 url_request_context_getter_,
660 &recorder_);
661 pending_registration_requests_[app_id] = registration_request;
662 registration_request->Start();
665 void GCMClientImpl::OnRegisterCompleted(
666 const std::string& app_id,
667 const std::vector<std::string>& sender_ids,
668 RegistrationRequest::Status status,
669 const std::string& registration_id) {
670 DCHECK(delegate_);
672 Result result;
673 PendingRegistrationRequests::iterator iter =
674 pending_registration_requests_.find(app_id);
675 if (iter == pending_registration_requests_.end())
676 result = UNKNOWN_ERROR;
677 else if (status == RegistrationRequest::INVALID_SENDER)
678 result = INVALID_PARAMETER;
679 else if (registration_id.empty())
680 result = SERVER_ERROR;
681 else
682 result = SUCCESS;
684 if (result == SUCCESS) {
685 // Cache it.
686 linked_ptr<RegistrationInfo> registration(new RegistrationInfo);
687 registration->sender_ids = sender_ids;
688 registration->registration_id = registration_id;
689 registrations_[app_id] = registration;
691 // Save it in the persistent store.
692 gcm_store_->AddRegistration(
693 app_id,
694 registration,
695 base::Bind(&GCMClientImpl::UpdateRegistrationCallback,
696 weak_ptr_factory_.GetWeakPtr()));
699 delegate_->OnRegisterFinished(
700 app_id, result == SUCCESS ? registration_id : std::string(), result);
702 if (iter != pending_registration_requests_.end()) {
703 delete iter->second;
704 pending_registration_requests_.erase(iter);
708 void GCMClientImpl::Unregister(const std::string& app_id) {
709 DCHECK_EQ(state_, READY);
710 if (pending_unregistration_requests_.count(app_id) == 1)
711 return;
713 // Remove from the cache and persistent store.
714 registrations_.erase(app_id);
715 gcm_store_->RemoveRegistration(
716 app_id,
717 base::Bind(&GCMClientImpl::UpdateRegistrationCallback,
718 weak_ptr_factory_.GetWeakPtr()));
720 UnregistrationRequest::RequestInfo request_info(
721 device_checkin_info_.android_id,
722 device_checkin_info_.secret,
723 app_id);
725 UnregistrationRequest* unregistration_request = new UnregistrationRequest(
726 gservices_settings_.GetRegistrationURL(),
727 request_info,
728 GetGCMBackoffPolicy(),
729 base::Bind(&GCMClientImpl::OnUnregisterCompleted,
730 weak_ptr_factory_.GetWeakPtr(),
731 app_id),
732 url_request_context_getter_,
733 &recorder_);
734 pending_unregistration_requests_[app_id] = unregistration_request;
735 unregistration_request->Start();
738 void GCMClientImpl::OnUnregisterCompleted(
739 const std::string& app_id,
740 UnregistrationRequest::Status status) {
741 DVLOG(1) << "Unregister completed for app: " << app_id
742 << " with " << (status ? "success." : "failure.");
743 delegate_->OnUnregisterFinished(
744 app_id,
745 status == UnregistrationRequest::SUCCESS ? SUCCESS : SERVER_ERROR);
747 PendingUnregistrationRequests::iterator iter =
748 pending_unregistration_requests_.find(app_id);
749 if (iter == pending_unregistration_requests_.end())
750 return;
752 delete iter->second;
753 pending_unregistration_requests_.erase(iter);
756 void GCMClientImpl::OnGCMStoreDestroyed(bool success) {
757 DLOG_IF(ERROR, !success) << "GCM store failed to be destroyed!";
758 UMA_HISTOGRAM_BOOLEAN("GCM.StoreDestroySucceeded", success);
761 void GCMClientImpl::Send(const std::string& app_id,
762 const std::string& receiver_id,
763 const OutgoingMessage& message) {
764 DCHECK_EQ(state_, READY);
766 RecordOutgoingMessageToUMA(message);
768 mcs_proto::DataMessageStanza stanza;
769 stanza.set_ttl(message.time_to_live);
770 stanza.set_sent(clock_->Now().ToInternalValue() /
771 base::Time::kMicrosecondsPerSecond);
772 stanza.set_id(message.id);
773 stanza.set_from(kSendMessageFromValue);
774 stanza.set_to(receiver_id);
775 stanza.set_category(app_id);
777 for (MessageData::const_iterator iter = message.data.begin();
778 iter != message.data.end();
779 ++iter) {
780 mcs_proto::AppData* app_data = stanza.add_app_data();
781 app_data->set_key(iter->first);
782 app_data->set_value(iter->second);
785 MCSMessage mcs_message(stanza);
786 DVLOG(1) << "MCS message size: " << mcs_message.size();
787 mcs_client_->SendMessage(mcs_message);
790 std::string GCMClientImpl::GetStateString() const {
791 switch(state_) {
792 case GCMClientImpl::INITIALIZED:
793 return "INITIALIZED";
794 case GCMClientImpl::UNINITIALIZED:
795 return "UNINITIALIZED";
796 case GCMClientImpl::LOADING:
797 return "LOADING";
798 case GCMClientImpl::INITIAL_DEVICE_CHECKIN:
799 return "INITIAL_DEVICE_CHECKIN";
800 case GCMClientImpl::READY:
801 return "READY";
802 default:
803 NOTREACHED();
804 return std::string();
808 void GCMClientImpl::SetRecording(bool recording) {
809 recorder_.SetRecording(recording);
812 void GCMClientImpl::ClearActivityLogs() {
813 recorder_.Clear();
816 GCMClient::GCMStatistics GCMClientImpl::GetStatistics() const {
817 GCMClient::GCMStatistics stats;
818 stats.gcm_client_created = true;
819 stats.is_recording = recorder_.is_recording();
820 stats.gcm_client_state = GetStateString();
821 stats.connection_client_created = mcs_client_.get() != NULL;
822 if (connection_factory_.get())
823 stats.connection_state = connection_factory_->GetConnectionStateString();
824 if (mcs_client_.get()) {
825 stats.send_queue_size = mcs_client_->GetSendQueueSize();
826 stats.resend_queue_size = mcs_client_->GetResendQueueSize();
828 if (device_checkin_info_.android_id > 0)
829 stats.android_id = device_checkin_info_.android_id;
830 recorder_.CollectActivities(&stats.recorded_activities);
832 for (RegistrationInfoMap::const_iterator it = registrations_.begin();
833 it != registrations_.end(); ++it) {
834 stats.registered_app_ids.push_back(it->first);
836 return stats;
839 void GCMClientImpl::OnActivityRecorded() {
840 delegate_->OnActivityRecorded();
843 void GCMClientImpl::OnConnected(const GURL& current_server,
844 const net::IPEndPoint& ip_endpoint) {
845 // TODO(gcm): expose current server in debug page.
846 delegate_->OnActivityRecorded();
847 delegate_->OnConnected(ip_endpoint);
850 void GCMClientImpl::OnDisconnected() {
851 delegate_->OnActivityRecorded();
852 delegate_->OnDisconnected();
855 void GCMClientImpl::OnMessageReceivedFromMCS(const gcm::MCSMessage& message) {
856 switch (message.tag()) {
857 case kLoginResponseTag:
858 DVLOG(1) << "Login response received by GCM Client. Ignoring.";
859 return;
860 case kDataMessageStanzaTag:
861 DVLOG(1) << "A downstream message received. Processing...";
862 HandleIncomingMessage(message);
863 return;
864 default:
865 NOTREACHED() << "Message with unexpected tag received by GCMClient";
866 return;
870 void GCMClientImpl::OnMessageSentToMCS(int64 user_serial_number,
871 const std::string& app_id,
872 const std::string& message_id,
873 MCSClient::MessageSendStatus status) {
874 DCHECK_EQ(user_serial_number, kDefaultUserSerialNumber);
875 DCHECK(delegate_);
877 // TTL_EXCEEDED is singled out here, because it can happen long time after the
878 // message was sent. That is why it comes as |OnMessageSendError| event rather
879 // than |OnSendFinished|. SendErrorDetails.additional_data is left empty.
880 // All other errors will be raised immediately, through asynchronous callback.
881 // It is expected that TTL_EXCEEDED will be issued for a message that was
882 // previously issued |OnSendFinished| with status SUCCESS.
883 // TODO(jianli): Consider adding UMA for this status.
884 if (status == MCSClient::TTL_EXCEEDED) {
885 SendErrorDetails send_error_details;
886 send_error_details.message_id = message_id;
887 send_error_details.result = GCMClient::TTL_EXCEEDED;
888 delegate_->OnMessageSendError(app_id, send_error_details);
889 } else if (status == MCSClient::SENT) {
890 delegate_->OnSendAcknowledged(app_id, message_id);
891 } else {
892 delegate_->OnSendFinished(app_id, message_id, ToGCMClientResult(status));
896 void GCMClientImpl::OnMCSError() {
897 // TODO(fgorski): For now it replaces the initialization method. Long term it
898 // should have an error or status passed in.
901 void GCMClientImpl::HandleIncomingMessage(const gcm::MCSMessage& message) {
902 DCHECK(delegate_);
904 const mcs_proto::DataMessageStanza& data_message_stanza =
905 reinterpret_cast<const mcs_proto::DataMessageStanza&>(
906 message.GetProtobuf());
907 DCHECK_EQ(data_message_stanza.device_user_id(), kDefaultUserSerialNumber);
909 // Copying all the data from the stanza to a MessageData object. When present,
910 // keys like kMessageTypeKey or kSendErrorMessageIdKey will be filtered out
911 // later.
912 MessageData message_data;
913 for (int i = 0; i < data_message_stanza.app_data_size(); ++i) {
914 std::string key = data_message_stanza.app_data(i).key();
915 message_data[key] = data_message_stanza.app_data(i).value();
918 MessageType message_type = DATA_MESSAGE;
919 MessageData::iterator iter = message_data.find(kMessageTypeKey);
920 if (iter != message_data.end()) {
921 message_type = DecodeMessageType(iter->second);
922 message_data.erase(iter);
925 switch (message_type) {
926 case DATA_MESSAGE:
927 HandleIncomingDataMessage(data_message_stanza, message_data);
928 break;
929 case DELETED_MESSAGES:
930 recorder_.RecordDataMessageReceived(data_message_stanza.category(),
931 data_message_stanza.from(),
932 data_message_stanza.ByteSize(),
933 true,
934 GCMStatsRecorder::DELETED_MESSAGES);
935 delegate_->OnMessagesDeleted(data_message_stanza.category());
936 break;
937 case SEND_ERROR:
938 HandleIncomingSendError(data_message_stanza, message_data);
939 break;
940 case UNKNOWN:
941 default: // Treat default the same as UNKNOWN.
942 DVLOG(1) << "Unknown message_type received. Message ignored. "
943 << "App ID: " << data_message_stanza.category() << ".";
944 break;
948 void GCMClientImpl::HandleIncomingDataMessage(
949 const mcs_proto::DataMessageStanza& data_message_stanza,
950 MessageData& message_data) {
951 std::string app_id = data_message_stanza.category();
953 // Drop the message when the app is not registered for the sender of the
954 // message.
955 RegistrationInfoMap::iterator iter = registrations_.find(app_id);
956 bool not_registered =
957 iter == registrations_.end() ||
958 std::find(iter->second->sender_ids.begin(),
959 iter->second->sender_ids.end(),
960 data_message_stanza.from()) == iter->second->sender_ids.end();
961 recorder_.RecordDataMessageReceived(app_id, data_message_stanza.from(),
962 data_message_stanza.ByteSize(), !not_registered,
963 GCMStatsRecorder::DATA_MESSAGE);
964 if (not_registered) {
965 return;
968 IncomingMessage incoming_message;
969 incoming_message.sender_id = data_message_stanza.from();
970 if (data_message_stanza.has_token())
971 incoming_message.collapse_key = data_message_stanza.token();
972 incoming_message.data = message_data;
973 delegate_->OnMessageReceived(app_id, incoming_message);
976 void GCMClientImpl::HandleIncomingSendError(
977 const mcs_proto::DataMessageStanza& data_message_stanza,
978 MessageData& message_data) {
979 SendErrorDetails send_error_details;
980 send_error_details.additional_data = message_data;
981 send_error_details.result = SERVER_ERROR;
983 MessageData::iterator iter =
984 send_error_details.additional_data.find(kSendErrorMessageIdKey);
985 if (iter != send_error_details.additional_data.end()) {
986 send_error_details.message_id = iter->second;
987 send_error_details.additional_data.erase(iter);
990 recorder_.RecordIncomingSendError(
991 data_message_stanza.category(),
992 data_message_stanza.to(),
993 data_message_stanza.id());
994 delegate_->OnMessageSendError(data_message_stanza.category(),
995 send_error_details);
998 } // namespace gcm