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