| // Copyright 2014 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "components/gcm_driver/gcm_client_impl.h" | 
 |  | 
 | #include <stddef.h> | 
 |  | 
 | #include <memory> | 
 | #include <utility> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/files/file_path.h" | 
 | #include "base/location.h" | 
 | #include "base/logging.h" | 
 | #include "base/memory/ptr_util.h" | 
 | #include "base/metrics/histogram_macros.h" | 
 | #include "base/sequenced_task_runner.h" | 
 | #include "base/single_thread_task_runner.h" | 
 | #include "base/stl_util.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "base/threading/thread_task_runner_handle.h" | 
 | #include "base/time/default_clock.h" | 
 | #include "base/timer/timer.h" | 
 | #include "components/crx_file/id_util.h" | 
 | #include "components/gcm_driver/crypto/gcm_decryption_result.h" | 
 | #include "components/gcm_driver/features.h" | 
 | #include "components/gcm_driver/gcm_account_mapper.h" | 
 | #include "components/gcm_driver/gcm_backoff_policy.h" | 
 | #include "google_apis/gcm/base/encryptor.h" | 
 | #include "google_apis/gcm/base/mcs_message.h" | 
 | #include "google_apis/gcm/base/mcs_util.h" | 
 | #include "google_apis/gcm/engine/checkin_request.h" | 
 | #include "google_apis/gcm/engine/connection_factory_impl.h" | 
 | #include "google_apis/gcm/engine/gcm_registration_request_handler.h" | 
 | #include "google_apis/gcm/engine/gcm_store_impl.h" | 
 | #include "google_apis/gcm/engine/gcm_unregistration_request_handler.h" | 
 | #include "google_apis/gcm/engine/instance_id_delete_token_request_handler.h" | 
 | #include "google_apis/gcm/engine/instance_id_get_token_request_handler.h" | 
 | #include "google_apis/gcm/monitoring/gcm_stats_recorder.h" | 
 | #include "google_apis/gcm/protocol/checkin.pb.h" | 
 | #include "google_apis/gcm/protocol/mcs.pb.h" | 
 | #include "services/network/public/cpp/shared_url_loader_factory.h" | 
 | #include "url/gurl.h" | 
 |  | 
 | namespace gcm { | 
 |  | 
 | // It is okay to append to the enum if these states grow. DO NOT reorder, | 
 | // renumber or otherwise reuse existing values. | 
 | // Do not assign an explicit value to REGISTRATION_CACHE_STATUS_COUNT, as | 
 | // this lets the compiler keep it up to date. | 
 | enum class RegistrationCacheStatus { | 
 |   REGISTRATION_NOT_FOUND = 0, | 
 |   REGISTRATION_FOUND_AND_FRESH = 1, | 
 |   REGISTRATION_FOUND_BUT_STALE = 2, | 
 |   REGISTRATION_FOUND_BUT_SENDERS_DONT_MATCH = 3, | 
 |   // NOTE: always keep this entry at the end. Add new value only immediately | 
 |   // above this line. Make sure to update the corresponding histogram enum | 
 |   // accordingly. | 
 |   REGISTRATION_CACHE_STATUS_COUNT | 
 | }; | 
 |  | 
 | namespace { | 
 |  | 
 | // Indicates a message type of the received message. | 
 | enum MessageType { | 
 |   UNKNOWN,           // Undetermined type. | 
 |   DATA_MESSAGE,      // Regular data message. | 
 |   DELETED_MESSAGES,  // Messages were deleted on the server. | 
 |   SEND_ERROR,        // Error sending a message. | 
 | }; | 
 |  | 
 | enum OutgoingMessageTTLCategory { | 
 |   TTL_ZERO, | 
 |   TTL_LESS_THAN_OR_EQUAL_TO_ONE_MINUTE, | 
 |   TTL_LESS_THAN_OR_EQUAL_TO_ONE_HOUR, | 
 |   TTL_LESS_THAN_OR_EQUAL_TO_ONE_DAY, | 
 |   TTL_LESS_THAN_OR_EQUAL_TO_ONE_WEEK, | 
 |   TTL_MORE_THAN_ONE_WEEK, | 
 |   TTL_MAXIMUM, | 
 |   // NOTE: always keep this entry at the end. Add new TTL category only | 
 |   // immediately above this line. Make sure to update the corresponding | 
 |   // histogram enum accordingly. | 
 |   TTL_CATEGORY_COUNT | 
 | }; | 
 |  | 
 | enum ResetStoreError { | 
 |   DESTROYING_STORE_FAILED, | 
 |   INFINITE_STORE_RESET, | 
 |   // NOTE: always keep this entry at the end. Add new value only immediately | 
 |   // above this line. Make sure to update the corresponding histogram enum | 
 |   // accordingly. | 
 |   RESET_STORE_ERROR_COUNT | 
 | }; | 
 |  | 
 | const int kMaxRegistrationRetries = 5; | 
 | const int kMaxUnregistrationRetries = 5; | 
 | const char kDeletedCountKey[] = "total_deleted"; | 
 | const char kMessageTypeDataMessage[] = "gcm"; | 
 | const char kMessageTypeDeletedMessagesKey[] = "deleted_messages"; | 
 | const char kMessageTypeKey[] = "message_type"; | 
 | const char kMessageTypeSendErrorKey[] = "send_error"; | 
 | const char kSendErrorMessageIdKey[] = "google.message_id"; | 
 | const char kSubtypeKey[] = "subtype"; | 
 | const char kSendMessageFromValue[] = "gcm@chrome.com"; | 
 | const int64_t kDefaultUserSerialNumber = 0LL; | 
 | const int kDestroyGCMStoreDelayMS = 5 * 60 * 1000;  // 5 minutes. | 
 |  | 
 | GCMClient::Result ToGCMClientResult(MCSClient::MessageSendStatus status) { | 
 |   switch (status) { | 
 |     case MCSClient::QUEUED: | 
 |       return GCMClient::SUCCESS; | 
 |     case MCSClient::MESSAGE_TOO_LARGE: | 
 |       return GCMClient::INVALID_PARAMETER; | 
 |     case MCSClient::QUEUE_SIZE_LIMIT_REACHED: | 
 |     case MCSClient::APP_QUEUE_SIZE_LIMIT_REACHED: | 
 |     case MCSClient::NO_CONNECTION_ON_ZERO_TTL: | 
 |     case MCSClient::TTL_EXCEEDED: | 
 |       return GCMClient::NETWORK_ERROR; | 
 |     case MCSClient::SENT: | 
 |     case MCSClient::SEND_STATUS_COUNT: | 
 |       NOTREACHED(); | 
 |       break; | 
 |   } | 
 |   return GCMClientImpl::UNKNOWN_ERROR; | 
 | } | 
 |  | 
 | void ToCheckinProtoVersion( | 
 |     const GCMClient::ChromeBuildInfo& chrome_build_info, | 
 |     checkin_proto::ChromeBuildProto* android_build_info) { | 
 |   checkin_proto::ChromeBuildProto_Platform platform = | 
 |       checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX; | 
 |   switch (chrome_build_info.platform) { | 
 |     case GCMClient::PLATFORM_WIN: | 
 |       platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_WIN; | 
 |       break; | 
 |     case GCMClient::PLATFORM_MAC: | 
 |       platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_MAC; | 
 |       break; | 
 |     case GCMClient::PLATFORM_LINUX: | 
 |       platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX; | 
 |       break; | 
 |     case GCMClient::PLATFORM_IOS: | 
 |       platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_IOS; | 
 |       break; | 
 |     case GCMClient::PLATFORM_ANDROID: | 
 |       platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_ANDROID; | 
 |       break; | 
 |     case GCMClient::PLATFORM_CROS: | 
 |       platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_CROS; | 
 |       break; | 
 |     case GCMClient::PLATFORM_UNSPECIFIED: | 
 |       // For unknown platform, return as LINUX. | 
 |       platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX; | 
 |       break; | 
 |   } | 
 |   android_build_info->set_platform(platform); | 
 |  | 
 |   checkin_proto::ChromeBuildProto_Channel channel = | 
 |       checkin_proto::ChromeBuildProto_Channel_CHANNEL_UNKNOWN; | 
 |   switch (chrome_build_info.channel) { | 
 |     case GCMClient::CHANNEL_STABLE: | 
 |       channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_STABLE; | 
 |       break; | 
 |     case GCMClient::CHANNEL_BETA: | 
 |       channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_BETA; | 
 |       break; | 
 |     case GCMClient::CHANNEL_DEV: | 
 |       channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_DEV; | 
 |       break; | 
 |     case GCMClient::CHANNEL_CANARY: | 
 |       channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_CANARY; | 
 |       break; | 
 |     case GCMClient::CHANNEL_UNKNOWN: | 
 |       channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_UNKNOWN; | 
 |       break; | 
 |   } | 
 |   android_build_info->set_channel(channel); | 
 |  | 
 |   android_build_info->set_chrome_version(chrome_build_info.version); | 
 | } | 
 |  | 
 | MessageType DecodeMessageType(const std::string& value) { | 
 |   if (kMessageTypeDeletedMessagesKey == value) | 
 |     return DELETED_MESSAGES; | 
 |   if (kMessageTypeSendErrorKey == value) | 
 |     return SEND_ERROR; | 
 |   if (kMessageTypeDataMessage == value) | 
 |     return DATA_MESSAGE; | 
 |   return UNKNOWN; | 
 | } | 
 |  | 
 | int ConstructGCMVersion(const std::string& chrome_version) { | 
 |   // Major Chrome version is passed as GCM version. | 
 |   size_t pos = chrome_version.find('.'); | 
 |   if (pos == std::string::npos) { | 
 |     NOTREACHED(); | 
 |     return 0; | 
 |   } | 
 |  | 
 |   int gcm_version = 0; | 
 |   base::StringToInt( | 
 |       base::StringPiece(chrome_version.c_str(), pos), &gcm_version); | 
 |   return gcm_version; | 
 | } | 
 |  | 
 | std::string SerializeInstanceIDData(const std::string& instance_id, | 
 |                                     const std::string& extra_data) { | 
 |   DCHECK(!instance_id.empty() && !extra_data.empty()); | 
 |   DCHECK(instance_id.find(',') == std::string::npos); | 
 |   return instance_id + "," + extra_data; | 
 | } | 
 |  | 
 | bool DeserializeInstanceIDData(const std::string& serialized_data, | 
 |                                std::string* instance_id, | 
 |                                std::string* extra_data) { | 
 |   DCHECK(instance_id && extra_data); | 
 |   std::size_t pos = serialized_data.find(','); | 
 |   if (pos == std::string::npos) | 
 |     return false; | 
 |   *instance_id = serialized_data.substr(0, pos); | 
 |   *extra_data = serialized_data.substr(pos + 1); | 
 |   return !instance_id->empty() && !extra_data->empty(); | 
 | } | 
 |  | 
 | bool InstanceIDUsesSubtypeForAppId(const std::string& app_id) { | 
 |   // Always use subtypes with Instance ID, except for Chrome Apps/Extensions. | 
 |   return !crx_file::id_util::IdIsValid(app_id); | 
 | } | 
 |  | 
 | void RecordOutgoingMessageToUMA(const gcm::OutgoingMessage& message) { | 
 |   OutgoingMessageTTLCategory ttl_category; | 
 |   if (message.time_to_live == 0) | 
 |     ttl_category = TTL_ZERO; | 
 |   else if (message.time_to_live <= 60 ) | 
 |     ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_MINUTE; | 
 |   else if (message.time_to_live <= 60 * 60) | 
 |     ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_HOUR; | 
 |   else if (message.time_to_live <= 24 * 60 * 60) | 
 |     ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_DAY; | 
 |   else | 
 |     ttl_category = TTL_MAXIMUM; | 
 |  | 
 |   UMA_HISTOGRAM_ENUMERATION("GCM.OutgoingMessageTTL", | 
 |                             ttl_category, | 
 |                             TTL_CATEGORY_COUNT); | 
 | } | 
 |  | 
 | void RecordResetStoreErrorToUMA(ResetStoreError error) { | 
 |   UMA_HISTOGRAM_ENUMERATION("GCM.ResetStore", error, RESET_STORE_ERROR_COUNT); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | void RecordRegistrationRequestToUMA(gcm::RegistrationCacheStatus status) { | 
 |   UMA_HISTOGRAM_ENUMERATION( | 
 |       "GCM.RegistrationCacheStatus", status, | 
 |       RegistrationCacheStatus::REGISTRATION_CACHE_STATUS_COUNT); | 
 | } | 
 | GCMInternalsBuilder::GCMInternalsBuilder() {} | 
 | GCMInternalsBuilder::~GCMInternalsBuilder() {} | 
 |  | 
 | base::Clock* GCMInternalsBuilder::GetClock() { | 
 |   return base::DefaultClock::GetInstance(); | 
 | } | 
 |  | 
 | std::unique_ptr<MCSClient> GCMInternalsBuilder::BuildMCSClient( | 
 |     const std::string& version, | 
 |     base::Clock* clock, | 
 |     ConnectionFactory* connection_factory, | 
 |     GCMStore* gcm_store, | 
 |     GCMStatsRecorder* recorder) { | 
 |   return std::unique_ptr<MCSClient>( | 
 |       new MCSClient(version, clock, connection_factory, gcm_store, recorder)); | 
 | } | 
 |  | 
 | std::unique_ptr<ConnectionFactory> GCMInternalsBuilder::BuildConnectionFactory( | 
 |     const std::vector<GURL>& endpoints, | 
 |     const net::BackoffEntry::Policy& backoff_policy, | 
 |     base::RepeatingCallback< | 
 |         void(network::mojom::ProxyResolvingSocketFactoryRequest)> | 
 |         get_socket_factory_callback, | 
 |     GCMStatsRecorder* recorder, | 
 |     network::NetworkConnectionTracker* network_connection_tracker) { | 
 |   return std::make_unique<ConnectionFactoryImpl>( | 
 |       endpoints, backoff_policy, std::move(get_socket_factory_callback), | 
 |       recorder, network_connection_tracker); | 
 | } | 
 |  | 
 | GCMClientImpl::CheckinInfo::CheckinInfo() | 
 |     : android_id(0), secret(0), accounts_set(false) { | 
 | } | 
 |  | 
 | GCMClientImpl::CheckinInfo::~CheckinInfo() { | 
 | } | 
 |  | 
 | void GCMClientImpl::CheckinInfo::SnapshotCheckinAccounts() { | 
 |   last_checkin_accounts.clear(); | 
 |   for (auto iter = account_tokens.begin(); iter != account_tokens.end(); | 
 |        ++iter) { | 
 |     last_checkin_accounts.insert(iter->first); | 
 |   } | 
 | } | 
 |  | 
 | void GCMClientImpl::CheckinInfo::Reset() { | 
 |   android_id = 0; | 
 |   secret = 0; | 
 |   accounts_set = false; | 
 |   account_tokens.clear(); | 
 |   last_checkin_accounts.clear(); | 
 | } | 
 |  | 
 | GCMClientImpl::GCMClientImpl( | 
 |     std::unique_ptr<GCMInternalsBuilder> internals_builder) | 
 |     : internals_builder_(std::move(internals_builder)), | 
 |       state_(UNINITIALIZED), | 
 |       delegate_(nullptr), | 
 |       start_mode_(DELAYED_START), | 
 |       clock_(internals_builder_->GetClock()), | 
 |       gcm_store_reset_(false), | 
 |       network_connection_tracker_(nullptr), | 
 |       periodic_checkin_ptr_factory_(this), | 
 |       destroying_gcm_store_ptr_factory_(this), | 
 |       weak_ptr_factory_(this) {} | 
 |  | 
 | GCMClientImpl::~GCMClientImpl() { | 
 | } | 
 |  | 
 | void GCMClientImpl::Initialize( | 
 |     const ChromeBuildInfo& chrome_build_info, | 
 |     const base::FilePath& path, | 
 |     const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner, | 
 |     base::RepeatingCallback< | 
 |         void(network::mojom::ProxyResolvingSocketFactoryRequest)> | 
 |         get_socket_factory_callback, | 
 |     const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory, | 
 |     network::NetworkConnectionTracker* network_connection_tracker, | 
 |     std::unique_ptr<Encryptor> encryptor, | 
 |     GCMClient::Delegate* delegate) { | 
 |   DCHECK_EQ(UNINITIALIZED, state_); | 
 |   DCHECK(delegate); | 
 |  | 
 |   get_socket_factory_callback_ = std::move(get_socket_factory_callback); | 
 |   url_loader_factory_ = url_loader_factory; | 
 |   network_connection_tracker_ = network_connection_tracker; | 
 |   chrome_build_info_ = chrome_build_info; | 
 |  | 
 |   gcm_store_.reset( | 
 |       new GCMStoreImpl(path, blocking_task_runner, std::move(encryptor))); | 
 |  | 
 |   delegate_ = delegate; | 
 |  | 
 |   recorder_.SetDelegate(this); | 
 |  | 
 |   state_ = INITIALIZED; | 
 | } | 
 |  | 
 | void GCMClientImpl::Start(StartMode start_mode) { | 
 |   DCHECK_NE(UNINITIALIZED, state_); | 
 |  | 
 |   if (state_ == LOADED) { | 
 |     // Start the GCM if not yet. | 
 |     if (start_mode == IMMEDIATE_START) { | 
 |       // Give up the scheduling to wipe out the store since now some one starts | 
 |       // to use GCM. | 
 |       destroying_gcm_store_ptr_factory_.InvalidateWeakPtrs(); | 
 |  | 
 |       StartGCM(); | 
 |     } | 
 |     return; | 
 |   } | 
 |  | 
 |   // The delay start behavior will be abandoned when Start has been called | 
 |   // once with IMMEDIATE_START behavior. | 
 |   if (start_mode == IMMEDIATE_START) | 
 |     start_mode_ = IMMEDIATE_START; | 
 |  | 
 |   // Bail out if the loading is not started or completed. | 
 |   if (state_ != INITIALIZED) | 
 |     return; | 
 |  | 
 |   // Once the loading is completed, the check-in will be initiated. | 
 |   // If we're in lazy start mode, don't create a new store since none is really | 
 |   // using GCM functionality yet. | 
 |   gcm_store_->Load( | 
 |       (start_mode == IMMEDIATE_START) ? | 
 |           GCMStore::CREATE_IF_MISSING : | 
 |           GCMStore::DO_NOT_CREATE, | 
 |       base::Bind(&GCMClientImpl::OnLoadCompleted, | 
 |                  weak_ptr_factory_.GetWeakPtr())); | 
 |   state_ = LOADING; | 
 | } | 
 |  | 
 | void GCMClientImpl::OnLoadCompleted( | 
 |     std::unique_ptr<GCMStore::LoadResult> result) { | 
 |   DCHECK_EQ(LOADING, state_); | 
 |  | 
 |   if (!result->success) { | 
 |     if (result->store_does_not_exist) { | 
 |       if (start_mode_ == IMMEDIATE_START) { | 
 |         // An immediate start was requested during the delayed start that just | 
 |         // completed. Perform it now. | 
 |         gcm_store_->Load(GCMStore::CREATE_IF_MISSING, | 
 |                          base::Bind(&GCMClientImpl::OnLoadCompleted, | 
 |                                     weak_ptr_factory_.GetWeakPtr())); | 
 |       } else { | 
 |         // In the case that the store does not exist, set |state_| back to | 
 |         // INITIALIZED such that store loading could be triggered again when | 
 |         // Start() is called with IMMEDIATE_START. | 
 |         state_ = INITIALIZED; | 
 |       } | 
 |     } else { | 
 |       // Otherwise, destroy the store to try again. | 
 |       ResetStore(); | 
 |     } | 
 |     return; | 
 |   } | 
 |   gcm_store_reset_ = false; | 
 |  | 
 |   device_checkin_info_.android_id = result->device_android_id; | 
 |   device_checkin_info_.secret = result->device_security_token; | 
 |   device_checkin_info_.last_checkin_accounts = result->last_checkin_accounts; | 
 |   // A case where there were previously no accounts reported with checkin is | 
 |   // considered to be the same as when the list of accounts is empty. It enables | 
 |   // scheduling a periodic checkin for devices with no signed in users | 
 |   // immediately after restart, while keeping |accounts_set == false| delays the | 
 |   // checkin until the list of accounts is set explicitly. | 
 |   if (result->last_checkin_accounts.size() == 0) | 
 |     device_checkin_info_.accounts_set = true; | 
 |   last_checkin_time_ = result->last_checkin_time; | 
 |   gservices_settings_.UpdateFromLoadResult(*result); | 
 |  | 
 |   for (auto iter = result->registrations.begin(); | 
 |        iter != result->registrations.end(); | 
 |        ++iter) { | 
 |     std::string registration_id; | 
 |     std::unique_ptr<RegistrationInfo> registration = | 
 |         RegistrationInfo::BuildFromString(iter->first, iter->second, | 
 |                                           ®istration_id); | 
 |     // TODO(jianli): Add UMA to track the error case. | 
 |     if (registration) | 
 |       registrations_[make_linked_ptr(registration.release())] = registration_id; | 
 |   } | 
 |  | 
 |   for (auto iter = result->instance_id_data.begin(); | 
 |        iter != result->instance_id_data.end(); | 
 |        ++iter) { | 
 |     std::string instance_id; | 
 |     std::string extra_data; | 
 |     if (DeserializeInstanceIDData(iter->second, &instance_id, &extra_data)) | 
 |       instance_id_data_[iter->first] = std::make_pair(instance_id, extra_data); | 
 |   } | 
 |  | 
 |   load_result_ = std::move(result); | 
 |   state_ = LOADED; | 
 |  | 
 |   // Don't initiate the GCM connection when GCM is in delayed start mode and | 
 |   // not any standalone app has registered GCM yet. | 
 |   if (start_mode_ == DELAYED_START && !HasStandaloneRegisteredApp()) { | 
 |     // If no standalone app is using GCM and the device ID is present, schedule | 
 |     // to have the store wiped out. | 
 |     if (device_checkin_info_.android_id) { | 
 |       base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | 
 |           FROM_HERE, | 
 |           base::BindOnce(&GCMClientImpl::DestroyStoreWhenNotNeeded, | 
 |                          destroying_gcm_store_ptr_factory_.GetWeakPtr()), | 
 |           base::TimeDelta::FromMilliseconds(kDestroyGCMStoreDelayMS)); | 
 |     } | 
 |  | 
 |     return; | 
 |   } | 
 |  | 
 |   StartGCM(); | 
 | } | 
 |  | 
 | void GCMClientImpl::StartGCM() { | 
 |   // Taking over the value of account_mappings before passing the ownership of | 
 |   // load result to InitializeMCSClient. | 
 |   std::vector<AccountMapping> account_mappings; | 
 |   account_mappings.swap(load_result_->account_mappings); | 
 |   base::Time last_token_fetch_time = load_result_->last_token_fetch_time; | 
 |  | 
 |   InitializeMCSClient(); | 
 |  | 
 |   if (device_checkin_info_.IsValid()) { | 
 |     SchedulePeriodicCheckin(); | 
 |     OnReady(account_mappings, last_token_fetch_time); | 
 |     return; | 
 |   } | 
 |  | 
 |   state_ = INITIAL_DEVICE_CHECKIN; | 
 |   device_checkin_info_.Reset(); | 
 |   StartCheckin(); | 
 | } | 
 |  | 
 | void GCMClientImpl::InitializeMCSClient() { | 
 |   DCHECK(network_connection_tracker_); | 
 |   std::vector<GURL> endpoints; | 
 |   endpoints.push_back(gservices_settings_.GetMCSMainEndpoint()); | 
 |   GURL fallback_endpoint = gservices_settings_.GetMCSFallbackEndpoint(); | 
 |   if (fallback_endpoint.is_valid()) | 
 |     endpoints.push_back(fallback_endpoint); | 
 |   connection_factory_ = internals_builder_->BuildConnectionFactory( | 
 |       endpoints, GetGCMBackoffPolicy(), get_socket_factory_callback_, | 
 |       &recorder_, network_connection_tracker_); | 
 |   connection_factory_->SetConnectionListener(this); | 
 |   mcs_client_ = internals_builder_->BuildMCSClient( | 
 |       chrome_build_info_.version, clock_, connection_factory_.get(), | 
 |       gcm_store_.get(), &recorder_); | 
 |  | 
 |   mcs_client_->Initialize( | 
 |       base::Bind(&GCMClientImpl::OnMCSError, weak_ptr_factory_.GetWeakPtr()), | 
 |       base::Bind(&GCMClientImpl::OnMessageReceivedFromMCS, | 
 |                  weak_ptr_factory_.GetWeakPtr()), | 
 |       base::Bind(&GCMClientImpl::OnMessageSentToMCS, | 
 |                  weak_ptr_factory_.GetWeakPtr()), | 
 |       std::move(load_result_)); | 
 | } | 
 |  | 
 | void GCMClientImpl::OnFirstTimeDeviceCheckinCompleted( | 
 |     const CheckinInfo& checkin_info) { | 
 |   DCHECK(!device_checkin_info_.IsValid()); | 
 |  | 
 |   device_checkin_info_.android_id = checkin_info.android_id; | 
 |   device_checkin_info_.secret = checkin_info.secret; | 
 |   // If accounts were not set by now, we can consider them set (to empty list) | 
 |   // to make sure periodic checkins get scheduled after initial checkin. | 
 |   device_checkin_info_.accounts_set = true; | 
 |   gcm_store_->SetDeviceCredentials( | 
 |       checkin_info.android_id, checkin_info.secret, | 
 |       base::Bind(&GCMClientImpl::SetDeviceCredentialsCallback, | 
 |                  weak_ptr_factory_.GetWeakPtr())); | 
 |  | 
 |   OnReady(std::vector<AccountMapping>(), base::Time()); | 
 | } | 
 |  | 
 | void GCMClientImpl::OnReady(const std::vector<AccountMapping>& account_mappings, | 
 |                             const base::Time& last_token_fetch_time) { | 
 |   state_ = READY; | 
 |   StartMCSLogin(); | 
 |  | 
 |   delegate_->OnGCMReady(account_mappings, last_token_fetch_time); | 
 | } | 
 |  | 
 | void GCMClientImpl::StartMCSLogin() { | 
 |   DCHECK_EQ(READY, state_); | 
 |   DCHECK(device_checkin_info_.IsValid()); | 
 |   mcs_client_->Login(device_checkin_info_.android_id, | 
 |                      device_checkin_info_.secret); | 
 | } | 
 |  | 
 | void GCMClientImpl::DestroyStoreWhenNotNeeded() { | 
 |   if (state_ != LOADED || start_mode_ != DELAYED_START) | 
 |     return; | 
 |  | 
 |   gcm_store_->Destroy(base::Bind(&GCMClientImpl::DestroyStoreCallback, | 
 |                       weak_ptr_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void GCMClientImpl::ResetStore() { | 
 |   // If already being reset, don't do it again. We want to prevent from | 
 |   // resetting and loading from the store again and again. | 
 |   if (gcm_store_reset_) { | 
 |     RecordResetStoreErrorToUMA(INFINITE_STORE_RESET); | 
 |     state_ = UNINITIALIZED; | 
 |     return; | 
 |   } | 
 |   gcm_store_reset_ = true; | 
 |  | 
 |   // Destroy the GCM store to start over. | 
 |   gcm_store_->Destroy(base::Bind(&GCMClientImpl::ResetStoreCallback, | 
 |                       weak_ptr_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void GCMClientImpl::SetAccountTokens( | 
 |     const std::vector<AccountTokenInfo>& account_tokens) { | 
 |   device_checkin_info_.account_tokens.clear(); | 
 |   for (auto iter = account_tokens.begin(); iter != account_tokens.end(); | 
 |        ++iter) { | 
 |     device_checkin_info_.account_tokens[iter->email] = iter->access_token; | 
 |   } | 
 |  | 
 |   bool accounts_set_before = device_checkin_info_.accounts_set; | 
 |   device_checkin_info_.accounts_set = true; | 
 |  | 
 |   DVLOG(1) << "Set account called with: " << account_tokens.size() | 
 |            << " accounts."; | 
 |  | 
 |   if (state_ != READY && state_ != INITIAL_DEVICE_CHECKIN) | 
 |     return; | 
 |  | 
 |   bool account_removed = false; | 
 |   for (auto iter = device_checkin_info_.last_checkin_accounts.begin(); | 
 |        iter != device_checkin_info_.last_checkin_accounts.end(); ++iter) { | 
 |     if (device_checkin_info_.account_tokens.find(*iter) == | 
 |             device_checkin_info_.account_tokens.end()) { | 
 |       account_removed = true; | 
 |     } | 
 |   } | 
 |  | 
 |   // Checkin will be forced when any of the accounts was removed during the | 
 |   // current Chrome session or if there has been an account removed between the | 
 |   // restarts of Chrome. If there is a checkin in progress, it will be canceled. | 
 |   // We only force checkin when user signs out. When there is a new account | 
 |   // signed in, the periodic checkin will take care of adding the association in | 
 |   // reasonable time. | 
 |   if (account_removed) { | 
 |     DVLOG(1) << "Detected that account has been removed. Forcing checkin."; | 
 |     checkin_request_.reset(); | 
 |     StartCheckin(); | 
 |   } else if (!accounts_set_before) { | 
 |     SchedulePeriodicCheckin(); | 
 |     DVLOG(1) << "Accounts set for the first time. Scheduled periodic checkin."; | 
 |   } | 
 | } | 
 |  | 
 | void GCMClientImpl::UpdateAccountMapping( | 
 |     const AccountMapping& account_mapping) { | 
 |   gcm_store_->AddAccountMapping(account_mapping, | 
 |                                 base::Bind(&GCMClientImpl::DefaultStoreCallback, | 
 |                                            weak_ptr_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void GCMClientImpl::RemoveAccountMapping(const std::string& account_id) { | 
 |   gcm_store_->RemoveAccountMapping( | 
 |       account_id, | 
 |       base::Bind(&GCMClientImpl::DefaultStoreCallback, | 
 |                  weak_ptr_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void GCMClientImpl::SetLastTokenFetchTime(const base::Time& time) { | 
 |   gcm_store_->SetLastTokenFetchTime( | 
 |       time, | 
 |       base::Bind(&GCMClientImpl::IgnoreWriteResultCallback, | 
 |                  weak_ptr_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void GCMClientImpl::UpdateHeartbeatTimer( | 
 |     std::unique_ptr<base::RetainingOneShotTimer> timer) { | 
 |   DCHECK(mcs_client_); | 
 |   mcs_client_->UpdateHeartbeatTimer(std::move(timer)); | 
 | } | 
 |  | 
 | void GCMClientImpl::AddInstanceIDData(const std::string& app_id, | 
 |                                       const std::string& instance_id, | 
 |                                       const std::string& extra_data) { | 
 |   instance_id_data_[app_id] = std::make_pair(instance_id, extra_data); | 
 |   gcm_store_->AddInstanceIDData( | 
 |       app_id, | 
 |       SerializeInstanceIDData(instance_id, extra_data), | 
 |       base::Bind(&GCMClientImpl::IgnoreWriteResultCallback, | 
 |                  weak_ptr_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void GCMClientImpl::RemoveInstanceIDData(const std::string& app_id) { | 
 |   instance_id_data_.erase(app_id); | 
 |   gcm_store_->RemoveInstanceIDData( | 
 |       app_id, | 
 |       base::Bind(&GCMClientImpl::IgnoreWriteResultCallback, | 
 |                  weak_ptr_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void GCMClientImpl::GetInstanceIDData(const std::string& app_id, | 
 |                                       std::string* instance_id, | 
 |                                       std::string* extra_data) { | 
 |   DCHECK(instance_id && extra_data); | 
 |  | 
 |   auto iter = instance_id_data_.find(app_id); | 
 |   if (iter == instance_id_data_.end()) | 
 |     return; | 
 |   *instance_id = iter->second.first; | 
 |   *extra_data = iter->second.second; | 
 | } | 
 |  | 
 | void GCMClientImpl::AddHeartbeatInterval(const std::string& scope, | 
 |                                          int interval_ms) { | 
 |   DCHECK(mcs_client_); | 
 |   mcs_client_->AddHeartbeatInterval(scope, interval_ms); | 
 | } | 
 |  | 
 | void GCMClientImpl::RemoveHeartbeatInterval(const std::string& scope) { | 
 |   DCHECK(mcs_client_); | 
 |   mcs_client_->RemoveHeartbeatInterval(scope); | 
 | } | 
 |  | 
 | void GCMClientImpl::StartCheckin() { | 
 |   // Make sure no checkin is in progress. | 
 |   if (checkin_request_) | 
 |     return; | 
 |  | 
 |   checkin_proto::ChromeBuildProto chrome_build_proto; | 
 |   ToCheckinProtoVersion(chrome_build_info_, &chrome_build_proto); | 
 |   CheckinRequest::RequestInfo request_info(device_checkin_info_.android_id, | 
 |                                            device_checkin_info_.secret, | 
 |                                            device_checkin_info_.account_tokens, | 
 |                                            gservices_settings_.digest(), | 
 |                                            chrome_build_proto); | 
 |   checkin_request_.reset(new CheckinRequest( | 
 |       gservices_settings_.GetCheckinURL(), request_info, GetGCMBackoffPolicy(), | 
 |       base::Bind(&GCMClientImpl::OnCheckinCompleted, | 
 |                  weak_ptr_factory_.GetWeakPtr()), | 
 |       url_loader_factory_, &recorder_)); | 
 |   // Taking a snapshot of the accounts count here, as there might be an asynch | 
 |   // update of the account tokens while checkin is in progress. | 
 |   device_checkin_info_.SnapshotCheckinAccounts(); | 
 |   checkin_request_->Start(); | 
 | } | 
 |  | 
 | void GCMClientImpl::OnCheckinCompleted( | 
 |     net::HttpStatusCode response_code, | 
 |     const checkin_proto::AndroidCheckinResponse& checkin_response) { | 
 |   checkin_request_.reset(); | 
 |  | 
 |   if (response_code == net::HTTP_UNAUTHORIZED || | 
 |       response_code == net::HTTP_BAD_REQUEST) { | 
 |     LOG(ERROR) << "Checkin rejected. Resetting GCM Store."; | 
 |     ResetStore(); | 
 |     return; | 
 |   } | 
 |  | 
 |   DCHECK(checkin_response.has_android_id()); | 
 |   DCHECK(checkin_response.has_security_token()); | 
 |   CheckinInfo checkin_info; | 
 |   checkin_info.android_id = checkin_response.android_id(); | 
 |   checkin_info.secret = checkin_response.security_token(); | 
 |  | 
 |   if (state_ == INITIAL_DEVICE_CHECKIN) { | 
 |     OnFirstTimeDeviceCheckinCompleted(checkin_info); | 
 |   } else { | 
 |     // checkin_info is not expected to change after a periodic checkin as it | 
 |     // would invalidate the registration IDs. | 
 |     DCHECK_EQ(READY, state_); | 
 |     DCHECK_EQ(device_checkin_info_.android_id, checkin_info.android_id); | 
 |     DCHECK_EQ(device_checkin_info_.secret, checkin_info.secret); | 
 |   } | 
 |  | 
 |   if (device_checkin_info_.IsValid()) { | 
 |     // First update G-services settings, as something might have changed. | 
 |     if (gservices_settings_.UpdateFromCheckinResponse(checkin_response)) { | 
 |       gcm_store_->SetGServicesSettings( | 
 |           gservices_settings_.settings_map(), | 
 |           gservices_settings_.digest(), | 
 |           base::Bind(&GCMClientImpl::SetGServicesSettingsCallback, | 
 |                      weak_ptr_factory_.GetWeakPtr())); | 
 |     } | 
 |  | 
 |     last_checkin_time_ = clock_->Now(); | 
 |     gcm_store_->SetLastCheckinInfo( | 
 |         last_checkin_time_, | 
 |         device_checkin_info_.last_checkin_accounts, | 
 |         base::Bind(&GCMClientImpl::SetLastCheckinInfoCallback, | 
 |                    weak_ptr_factory_.GetWeakPtr())); | 
 |     SchedulePeriodicCheckin(); | 
 |   } | 
 | } | 
 |  | 
 | void GCMClientImpl::SetGServicesSettingsCallback(bool success) { | 
 |   DCHECK(success); | 
 | } | 
 |  | 
 | void GCMClientImpl::SchedulePeriodicCheckin() { | 
 |   // Make sure no checkin is in progress. | 
 |   if (checkin_request_.get() || !device_checkin_info_.accounts_set) | 
 |     return; | 
 |  | 
 |   // There should be only one periodic checkin pending at a time. Removing | 
 |   // pending periodic checkin to schedule a new one. | 
 |   periodic_checkin_ptr_factory_.InvalidateWeakPtrs(); | 
 |  | 
 |   base::TimeDelta time_to_next_checkin = GetTimeToNextCheckin(); | 
 |   if (time_to_next_checkin < base::TimeDelta()) | 
 |     time_to_next_checkin = base::TimeDelta(); | 
 |  | 
 |   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | 
 |       FROM_HERE, | 
 |       base::BindOnce(&GCMClientImpl::StartCheckin, | 
 |                      periodic_checkin_ptr_factory_.GetWeakPtr()), | 
 |       time_to_next_checkin); | 
 | } | 
 |  | 
 | base::TimeDelta GCMClientImpl::GetTimeToNextCheckin() const { | 
 |   return last_checkin_time_ + gservices_settings_.GetCheckinInterval() - | 
 |          clock_->Now(); | 
 | } | 
 |  | 
 | void GCMClientImpl::SetLastCheckinInfoCallback(bool success) { | 
 |   // TODO(fgorski): This is one of the signals that store needs a rebuild. | 
 |   DCHECK(success); | 
 | } | 
 |  | 
 | void GCMClientImpl::SetDeviceCredentialsCallback(bool success) { | 
 |   // TODO(fgorski): This is one of the signals that store needs a rebuild. | 
 |   DCHECK(success); | 
 | } | 
 |  | 
 | void GCMClientImpl::UpdateRegistrationCallback(bool success) { | 
 |   // TODO(fgorski): This is one of the signals that store needs a rebuild. | 
 |   DCHECK(success); | 
 | } | 
 |  | 
 | void GCMClientImpl::DefaultStoreCallback(bool success) { | 
 |   DCHECK(success); | 
 | } | 
 |  | 
 | void GCMClientImpl::IgnoreWriteResultCallback(bool success) { | 
 |   // TODO(fgorski): Ignoring the write result for now to make sure | 
 |   // sync_intergration_tests are not broken. | 
 | } | 
 |  | 
 | void GCMClientImpl::DestroyStoreCallback(bool success) { | 
 |   ResetCache(); | 
 |  | 
 |   if (!success) { | 
 |     LOG(ERROR) << "Failed to destroy GCM store"; | 
 |     RecordResetStoreErrorToUMA(DESTROYING_STORE_FAILED); | 
 |     state_ = UNINITIALIZED; | 
 |     return; | 
 |   } | 
 |  | 
 |   state_ = INITIALIZED; | 
 | } | 
 |  | 
 | void GCMClientImpl::ResetStoreCallback(bool success) { | 
 |   // Even an incomplete reset may invalidate registrations, and this might be | 
 |   // the only opportunity to notify the delegate. For example a partial reset | 
 |   // that deletes the "CURRENT" file will cause GCMStoreImpl to consider the DB | 
 |   // to no longer exist, in which case the next load will simply create a new | 
 |   // store rather than resetting it. | 
 |   delegate_->OnStoreReset(); | 
 |  | 
 |   if (!success) { | 
 |     LOG(ERROR) << "Failed to reset GCM store"; | 
 |     RecordResetStoreErrorToUMA(DESTROYING_STORE_FAILED); | 
 |     state_ = UNINITIALIZED; | 
 |     return; | 
 |   } | 
 |  | 
 |   state_ = INITIALIZED; | 
 |   Start(start_mode_); | 
 | } | 
 |  | 
 | void GCMClientImpl::Stop() { | 
 |   // TODO(fgorski): Perhaps we should make a distinction between a Stop and a | 
 |   // Shutdown. | 
 |   DVLOG(1) << "Stopping the GCM Client"; | 
 |   ResetCache(); | 
 |   state_ = INITIALIZED; | 
 |   gcm_store_->Close(); | 
 | } | 
 |  | 
 | void GCMClientImpl::ResetCache() { | 
 |   weak_ptr_factory_.InvalidateWeakPtrs(); | 
 |   periodic_checkin_ptr_factory_.InvalidateWeakPtrs(); | 
 |   device_checkin_info_.Reset(); | 
 |   connection_factory_.reset(); | 
 |   delegate_->OnDisconnected(); | 
 |   mcs_client_.reset(); | 
 |   checkin_request_.reset(); | 
 |   // Delete all of the pending registration and unregistration requests. | 
 |   pending_registration_requests_.clear(); | 
 |   pending_unregistration_requests_.clear(); | 
 | } | 
 |  | 
 | void GCMClientImpl::Register( | 
 |     const linked_ptr<RegistrationInfo>& registration_info) { | 
 |   DCHECK_EQ(state_, READY); | 
 |  | 
 |   // Registrations should never pass as an app_id the special category used | 
 |   // internally when registering with a subtype. See security note in | 
 |   // GCMClientImpl::HandleIncomingMessage. | 
 |   CHECK_NE(registration_info->app_id, | 
 |            chrome_build_info_.product_category_for_subtypes); | 
 |  | 
 |   // Find and use the cached registration ID. | 
 |   RegistrationInfoMap::const_iterator registrations_iter = | 
 |       registrations_.find(registration_info); | 
 |   if (registrations_iter != registrations_.end()) { | 
 |     bool matched = true; | 
 |  | 
 |     // For GCM registration, we also match the sender IDs since multiple | 
 |     // registrations are not supported. | 
 |     const GCMRegistrationInfo* gcm_registration_info = | 
 |         GCMRegistrationInfo::FromRegistrationInfo(registration_info.get()); | 
 |     if (gcm_registration_info) { | 
 |       const GCMRegistrationInfo* cached_gcm_registration_info = | 
 |           GCMRegistrationInfo::FromRegistrationInfo( | 
 |               registrations_iter->first.get()); | 
 |       DCHECK(cached_gcm_registration_info); | 
 |       if (gcm_registration_info->sender_ids != | 
 |           cached_gcm_registration_info->sender_ids) { | 
 |         matched = false; | 
 |         // Senders IDs don't match existing registration. | 
 |         RecordRegistrationRequestToUMA( | 
 |             RegistrationCacheStatus::REGISTRATION_FOUND_BUT_SENDERS_DONT_MATCH); | 
 |       } | 
 |     } | 
 |  | 
 |     if (matched) { | 
 |       // Skip registration if token is fresh. | 
 |       base::TimeDelta token_invalidation_period = | 
 |           features::GetTokenInvalidationInterval(); | 
 |       base::TimeDelta time_since_last_validation = | 
 |           clock_->Now() - registrations_iter->first->last_validated; | 
 |       if (token_invalidation_period.is_zero() || | 
 |           time_since_last_validation < token_invalidation_period) { | 
 |         // Token is fresh, or token invalidation is disabled. | 
 |         // Use cached registration. | 
 |         delegate_->OnRegisterFinished(registration_info, | 
 |                                       registrations_iter->second, SUCCESS); | 
 |         RecordRegistrationRequestToUMA( | 
 |             RegistrationCacheStatus::REGISTRATION_FOUND_AND_FRESH); | 
 |         return; | 
 |       } else { | 
 |         // Token is stale. | 
 |         RecordRegistrationRequestToUMA( | 
 |             RegistrationCacheStatus::REGISTRATION_FOUND_BUT_STALE); | 
 |       } | 
 |     } | 
 |   } else { | 
 |     // New Registration request (no existing registration) | 
 |     RecordRegistrationRequestToUMA( | 
 |         RegistrationCacheStatus::REGISTRATION_NOT_FOUND); | 
 |   } | 
 |  | 
 |   std::unique_ptr<RegistrationRequest::CustomRequestHandler> request_handler; | 
 |   std::string source_to_record; | 
 |  | 
 |   const GCMRegistrationInfo* gcm_registration_info = | 
 |       GCMRegistrationInfo::FromRegistrationInfo(registration_info.get()); | 
 |   if (gcm_registration_info) { | 
 |     std::string senders; | 
 |     for (auto iter = gcm_registration_info->sender_ids.begin(); | 
 |          iter != gcm_registration_info->sender_ids.end(); | 
 |          ++iter) { | 
 |       if (!senders.empty()) | 
 |         senders.append(","); | 
 |       senders.append(*iter); | 
 |     } | 
 |     UMA_HISTOGRAM_COUNTS_1M("GCM.RegistrationSenderIdCount", | 
 |                             gcm_registration_info->sender_ids.size()); | 
 |  | 
 |     request_handler.reset(new GCMRegistrationRequestHandler(senders)); | 
 |     source_to_record = senders; | 
 |   } | 
 |  | 
 |   const InstanceIDTokenInfo* instance_id_token_info = | 
 |       InstanceIDTokenInfo::FromRegistrationInfo(registration_info.get()); | 
 |   if (instance_id_token_info) { | 
 |     auto instance_id_iter = instance_id_data_.find(registration_info->app_id); | 
 |     DCHECK(instance_id_iter != instance_id_data_.end()); | 
 |  | 
 |     request_handler.reset(new InstanceIDGetTokenRequestHandler( | 
 |           instance_id_iter->second.first, | 
 |           instance_id_token_info->authorized_entity, | 
 |           instance_id_token_info->scope, | 
 |           ConstructGCMVersion(chrome_build_info_.version), | 
 |           instance_id_token_info->options)); | 
 |     source_to_record = instance_id_token_info->authorized_entity + "/" + | 
 |                        instance_id_token_info->scope; | 
 |   } | 
 |  | 
 |   bool use_subtype = instance_id_token_info && | 
 |                      InstanceIDUsesSubtypeForAppId(registration_info->app_id); | 
 |   std::string category = use_subtype | 
 |                              ? chrome_build_info_.product_category_for_subtypes | 
 |                              : registration_info->app_id; | 
 |   std::string subtype = use_subtype ? registration_info->app_id : std::string(); | 
 |   RegistrationRequest::RequestInfo request_info(device_checkin_info_.android_id, | 
 |                                                 device_checkin_info_.secret, | 
 |                                                 category, subtype); | 
 |  | 
 |   std::unique_ptr<RegistrationRequest> registration_request( | 
 |       new RegistrationRequest( | 
 |           gservices_settings_.GetRegistrationURL(), request_info, | 
 |           std::move(request_handler), GetGCMBackoffPolicy(), | 
 |           base::Bind(&GCMClientImpl::OnRegisterCompleted, | 
 |                      weak_ptr_factory_.GetWeakPtr(), registration_info), | 
 |           kMaxRegistrationRetries, url_loader_factory_, &recorder_, | 
 |           source_to_record)); | 
 |   registration_request->Start(); | 
 |   pending_registration_requests_.insert( | 
 |       std::make_pair(registration_info, std::move(registration_request))); | 
 | } | 
 |  | 
 | void GCMClientImpl::OnRegisterCompleted( | 
 |     const linked_ptr<RegistrationInfo>& registration_info, | 
 |     RegistrationRequest::Status status, | 
 |     const std::string& registration_id) { | 
 |   DCHECK(delegate_); | 
 |  | 
 |   Result result; | 
 |   PendingRegistrationRequests::const_iterator iter = | 
 |       pending_registration_requests_.find(registration_info); | 
 |   if (iter == pending_registration_requests_.end()) { | 
 |     result = UNKNOWN_ERROR; | 
 |   } else if (status == RegistrationRequest::INVALID_SENDER) { | 
 |     result = INVALID_PARAMETER; | 
 |   } else if (registration_id.empty()) { | 
 |     // All other errors are currently treated as SERVER_ERROR (including | 
 |     // REACHED_MAX_RETRIES due to the device being offline!). | 
 |     result = SERVER_ERROR; | 
 |   } else { | 
 |     result = SUCCESS; | 
 |   } | 
 |  | 
 |   if (result == SUCCESS) { | 
 |     // Cache it. | 
 |     // Note that the existing cached record has to be removed first because | 
 |     // otherwise the key value in registrations_ will not be updated. For GCM | 
 |     // registrations, the key consists of pair of app_id and sender_ids though | 
 |     // only app_id is used in the key comparison. | 
 |     registrations_.erase(registration_info); | 
 |     registration_info->last_validated = clock_->Now(); | 
 |     registrations_[registration_info] = registration_id; | 
 |  | 
 |     // Save it in the persistent store. | 
 |     gcm_store_->AddRegistration( | 
 |         registration_info->GetSerializedKey(), | 
 |         registration_info->GetSerializedValue(registration_id), | 
 |         base::Bind(&GCMClientImpl::UpdateRegistrationCallback, | 
 |                    weak_ptr_factory_.GetWeakPtr())); | 
 |   } | 
 |  | 
 |   delegate_->OnRegisterFinished( | 
 |       registration_info, | 
 |       result == SUCCESS ? registration_id : std::string(), | 
 |       result); | 
 |  | 
 |   if (iter != pending_registration_requests_.end()) | 
 |     pending_registration_requests_.erase(iter); | 
 | } | 
 |  | 
 | bool GCMClientImpl::ValidateRegistration( | 
 |     const linked_ptr<RegistrationInfo>& registration_info, | 
 |     const std::string& registration_id) { | 
 |   DCHECK_EQ(state_, READY); | 
 |  | 
 |   // Must have a cached registration. | 
 |   RegistrationInfoMap::const_iterator registrations_iter = | 
 |       registrations_.find(registration_info); | 
 |   if (registrations_iter == registrations_.end()) | 
 |     return false; | 
 |  | 
 |   // Cached registration ID must match. | 
 |   const std::string& cached_registration_id = registrations_iter->second; | 
 |   if (registration_id != cached_registration_id) | 
 |     return false; | 
 |  | 
 |   // For GCM registration, we also match the sender IDs since multiple | 
 |   // registrations are not supported. | 
 |   const GCMRegistrationInfo* gcm_registration_info = | 
 |       GCMRegistrationInfo::FromRegistrationInfo(registration_info.get()); | 
 |   if (gcm_registration_info) { | 
 |     const GCMRegistrationInfo* cached_gcm_registration_info = | 
 |         GCMRegistrationInfo::FromRegistrationInfo( | 
 |             registrations_iter->first.get()); | 
 |     DCHECK(cached_gcm_registration_info); | 
 |     if (gcm_registration_info->sender_ids != | 
 |         cached_gcm_registration_info->sender_ids) { | 
 |       return false; | 
 |     } | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | void GCMClientImpl::Unregister( | 
 |     const linked_ptr<RegistrationInfo>& registration_info) { | 
 |   DCHECK_EQ(state_, READY); | 
 |  | 
 |   std::unique_ptr<UnregistrationRequest::CustomRequestHandler> request_handler; | 
 |   std::string source_to_record; | 
 |  | 
 |   const GCMRegistrationInfo* gcm_registration_info = | 
 |       GCMRegistrationInfo::FromRegistrationInfo(registration_info.get()); | 
 |   if (gcm_registration_info) { | 
 |     request_handler.reset( | 
 |         new GCMUnregistrationRequestHandler(registration_info->app_id)); | 
 |   } | 
 |  | 
 |   const InstanceIDTokenInfo* instance_id_token_info = | 
 |       InstanceIDTokenInfo::FromRegistrationInfo(registration_info.get()); | 
 |   if (instance_id_token_info) { | 
 |     auto instance_id_iter = instance_id_data_.find(registration_info->app_id); | 
 |     if (instance_id_iter == instance_id_data_.end()) { | 
 |       // This should not be reached since we should not delete tokens when | 
 |       // an InstanceID has not been created yet. | 
 |       NOTREACHED(); | 
 |       return; | 
 |     } | 
 |  | 
 |     request_handler.reset(new InstanceIDDeleteTokenRequestHandler( | 
 |         instance_id_iter->second.first, | 
 |         instance_id_token_info->authorized_entity, | 
 |         instance_id_token_info->scope, | 
 |         ConstructGCMVersion(chrome_build_info_.version))); | 
 |     source_to_record = instance_id_token_info->authorized_entity + "/" + | 
 |                        instance_id_token_info->scope; | 
 |   } | 
 |  | 
 |   // Remove the registration/token(s) from the cache and the store. | 
 |   // TODO(jianli): Remove it only when the request is successful. | 
 |   if (instance_id_token_info && | 
 |       instance_id_token_info->authorized_entity == "*" && | 
 |       instance_id_token_info->scope == "*") { | 
 |     // If authorized_entity and scope are '*', find and remove all associated | 
 |     // tokens. | 
 |     bool token_found = false; | 
 |     for (auto iter = registrations_.begin(); | 
 |           iter != registrations_.end();) { | 
 |       InstanceIDTokenInfo* cached_instance_id_token_info = | 
 |           InstanceIDTokenInfo::FromRegistrationInfo(iter->first.get()); | 
 |       if (cached_instance_id_token_info && | 
 |           cached_instance_id_token_info->app_id == registration_info->app_id) { | 
 |         token_found = true; | 
 |         gcm_store_->RemoveRegistration( | 
 |             cached_instance_id_token_info->GetSerializedKey(), | 
 |             base::Bind(&GCMClientImpl::UpdateRegistrationCallback, | 
 |                         weak_ptr_factory_.GetWeakPtr())); | 
 |         registrations_.erase(iter++); | 
 |       } else { | 
 |         ++iter; | 
 |       } | 
 |     } | 
 |  | 
 |     // If no token is found for the Instance ID, don't need to unregister | 
 |     // since the Instance ID is not sent to the server yet. | 
 |     if (!token_found) { | 
 |       OnUnregisterCompleted(registration_info, | 
 |                             UnregistrationRequest::SUCCESS); | 
 |       return; | 
 |     } | 
 |   } else { | 
 |     auto iter = registrations_.find(registration_info); | 
 |     if (iter == registrations_.end()) { | 
 |       delegate_->OnUnregisterFinished(registration_info, INVALID_PARAMETER); | 
 |       return; | 
 |     } | 
 |     registrations_.erase(iter); | 
 |  | 
 |     gcm_store_->RemoveRegistration( | 
 |       registration_info->GetSerializedKey(), | 
 |       base::Bind(&GCMClientImpl::UpdateRegistrationCallback, | 
 |                   weak_ptr_factory_.GetWeakPtr())); | 
 |   } | 
 |  | 
 |   bool use_subtype = instance_id_token_info && | 
 |                      InstanceIDUsesSubtypeForAppId(registration_info->app_id); | 
 |   std::string category = use_subtype | 
 |                              ? chrome_build_info_.product_category_for_subtypes | 
 |                              : registration_info->app_id; | 
 |   std::string subtype = use_subtype ? registration_info->app_id : std::string(); | 
 |   UnregistrationRequest::RequestInfo request_info( | 
 |       device_checkin_info_.android_id, device_checkin_info_.secret, category, | 
 |       subtype); | 
 |  | 
 |   std::unique_ptr<UnregistrationRequest> unregistration_request( | 
 |       new UnregistrationRequest( | 
 |           gservices_settings_.GetRegistrationURL(), request_info, | 
 |           std::move(request_handler), GetGCMBackoffPolicy(), | 
 |           base::Bind(&GCMClientImpl::OnUnregisterCompleted, | 
 |                      weak_ptr_factory_.GetWeakPtr(), registration_info), | 
 |           kMaxUnregistrationRetries, url_loader_factory_, &recorder_, | 
 |           source_to_record)); | 
 |   unregistration_request->Start(); | 
 |   pending_unregistration_requests_.insert( | 
 |       std::make_pair(registration_info, std::move(unregistration_request))); | 
 | } | 
 |  | 
 | void GCMClientImpl::OnUnregisterCompleted( | 
 |     const linked_ptr<RegistrationInfo>& registration_info, | 
 |     UnregistrationRequest::Status status) { | 
 |   DVLOG(1) << "Unregister completed for app: " << registration_info->app_id | 
 |            << " with " << (status ? "success." : "failure."); | 
 |  | 
 |   Result result; | 
 |   switch (status) { | 
 |     case UnregistrationRequest::SUCCESS: | 
 |       result = SUCCESS; | 
 |       break; | 
 |     case UnregistrationRequest::INVALID_PARAMETERS: | 
 |       result = INVALID_PARAMETER; | 
 |       break; | 
 |     default: | 
 |       // All other errors are currently treated as SERVER_ERROR (including | 
 |       // REACHED_MAX_RETRIES due to the device being offline!). | 
 |       result = SERVER_ERROR; | 
 |       break; | 
 |   } | 
 |   delegate_->OnUnregisterFinished(registration_info, result); | 
 |  | 
 |   pending_unregistration_requests_.erase(registration_info); | 
 | } | 
 |  | 
 | void GCMClientImpl::OnGCMStoreDestroyed(bool success) { | 
 |   DLOG_IF(ERROR, !success) << "GCM store failed to be destroyed!"; | 
 |   UMA_HISTOGRAM_BOOLEAN("GCM.StoreDestroySucceeded", success); | 
 | } | 
 |  | 
 | void GCMClientImpl::Send(const std::string& app_id, | 
 |                          const std::string& receiver_id, | 
 |                          const OutgoingMessage& message) { | 
 |   DCHECK_EQ(state_, READY); | 
 |  | 
 |   RecordOutgoingMessageToUMA(message); | 
 |  | 
 |   mcs_proto::DataMessageStanza stanza; | 
 |   stanza.set_ttl(message.time_to_live); | 
 |   stanza.set_sent(clock_->Now().ToInternalValue() / | 
 |                   base::Time::kMicrosecondsPerSecond); | 
 |   stanza.set_id(message.id); | 
 |   stanza.set_from(kSendMessageFromValue); | 
 |   stanza.set_to(receiver_id); | 
 |   stanza.set_category(app_id); | 
 |  | 
 |   for (auto iter = message.data.begin(); iter != message.data.end(); ++iter) { | 
 |     mcs_proto::AppData* app_data = stanza.add_app_data(); | 
 |     app_data->set_key(iter->first); | 
 |     app_data->set_value(iter->second); | 
 |   } | 
 |  | 
 |   MCSMessage mcs_message(stanza); | 
 |   DVLOG(1) << "MCS message size: " << mcs_message.size(); | 
 |   mcs_client_->SendMessage(mcs_message); | 
 | } | 
 |  | 
 | std::string GCMClientImpl::GetStateString() const { | 
 |   switch(state_) { | 
 |     case GCMClientImpl::UNINITIALIZED: | 
 |       return "UNINITIALIZED"; | 
 |     case GCMClientImpl::INITIALIZED: | 
 |       return "INITIALIZED"; | 
 |     case GCMClientImpl::LOADING: | 
 |       return "LOADING"; | 
 |     case GCMClientImpl::LOADED: | 
 |       return "LOADED"; | 
 |     case GCMClientImpl::INITIAL_DEVICE_CHECKIN: | 
 |       return "INITIAL_DEVICE_CHECKIN"; | 
 |     case GCMClientImpl::READY: | 
 |       return "READY"; | 
 |   } | 
 |   NOTREACHED(); | 
 |   return std::string(); | 
 | } | 
 |  | 
 | void GCMClientImpl::RecordDecryptionFailure(const std::string& app_id, | 
 |                                             GCMDecryptionResult result) { | 
 |   recorder_.RecordDecryptionFailure(app_id, result); | 
 | } | 
 |  | 
 | void GCMClientImpl::SetRecording(bool recording) { | 
 |   recorder_.set_is_recording(recording); | 
 | } | 
 |  | 
 | void GCMClientImpl::ClearActivityLogs() { | 
 |   recorder_.Clear(); | 
 | } | 
 |  | 
 | GCMClient::GCMStatistics GCMClientImpl::GetStatistics() const { | 
 |   GCMClient::GCMStatistics stats; | 
 |   stats.gcm_client_created = true; | 
 |   stats.is_recording = recorder_.is_recording(); | 
 |   stats.gcm_client_state = GetStateString(); | 
 |   stats.connection_client_created = mcs_client_ != nullptr; | 
 |   stats.last_checkin = last_checkin_time_; | 
 |   stats.next_checkin = | 
 |       last_checkin_time_ + gservices_settings_.GetCheckinInterval(); | 
 |   if (connection_factory_) | 
 |     stats.connection_state = connection_factory_->GetConnectionStateString(); | 
 |   if (mcs_client_) { | 
 |     stats.send_queue_size = mcs_client_->GetSendQueueSize(); | 
 |     stats.resend_queue_size = mcs_client_->GetResendQueueSize(); | 
 |   } | 
 |   if (device_checkin_info_.android_id > 0) | 
 |     stats.android_id = device_checkin_info_.android_id; | 
 |   if (device_checkin_info_.secret > 0) | 
 |     stats.android_secret = device_checkin_info_.secret; | 
 |  | 
 |   recorder_.CollectActivities(&stats.recorded_activities); | 
 |  | 
 |   for (auto it = registrations_.begin(); it != registrations_.end(); ++it) { | 
 |     stats.registered_app_ids.push_back(it->first->app_id); | 
 |   } | 
 |   return stats; | 
 | } | 
 |  | 
 | void GCMClientImpl::OnActivityRecorded() { | 
 |   delegate_->OnActivityRecorded(); | 
 | } | 
 |  | 
 | void GCMClientImpl::OnConnected(const GURL& current_server, | 
 |                                 const net::IPEndPoint& ip_endpoint) { | 
 |   // TODO(gcm): expose current server in debug page. | 
 |   delegate_->OnActivityRecorded(); | 
 |   delegate_->OnConnected(ip_endpoint); | 
 | } | 
 |  | 
 | void GCMClientImpl::OnDisconnected() { | 
 |   delegate_->OnActivityRecorded(); | 
 |   delegate_->OnDisconnected(); | 
 | } | 
 |  | 
 | void GCMClientImpl::OnMessageReceivedFromMCS(const gcm::MCSMessage& message) { | 
 |   switch (message.tag()) { | 
 |     case kLoginResponseTag: | 
 |       DVLOG(1) << "Login response received by GCM Client. Ignoring."; | 
 |       return; | 
 |     case kDataMessageStanzaTag: | 
 |       DVLOG(1) << "A downstream message received. Processing..."; | 
 |       HandleIncomingMessage(message); | 
 |       return; | 
 |     default: | 
 |       NOTREACHED() << "Message with unexpected tag received by GCMClient"; | 
 |       return; | 
 |   } | 
 | } | 
 |  | 
 | void GCMClientImpl::OnMessageSentToMCS(int64_t user_serial_number, | 
 |                                        const std::string& app_id, | 
 |                                        const std::string& message_id, | 
 |                                        MCSClient::MessageSendStatus status) { | 
 |   DCHECK_EQ(user_serial_number, kDefaultUserSerialNumber); | 
 |   DCHECK(delegate_); | 
 |  | 
 |   // TTL_EXCEEDED is singled out here, because it can happen long time after the | 
 |   // message was sent. That is why it comes as |OnMessageSendError| event rather | 
 |   // than |OnSendFinished|. SendErrorDetails.additional_data is left empty. | 
 |   // All other errors will be raised immediately, through asynchronous callback. | 
 |   // It is expected that TTL_EXCEEDED will be issued for a message that was | 
 |   // previously issued |OnSendFinished| with status SUCCESS. | 
 |   // TODO(jianli): Consider adding UMA for this status. | 
 |   if (status == MCSClient::TTL_EXCEEDED) { | 
 |     SendErrorDetails send_error_details; | 
 |     send_error_details.message_id = message_id; | 
 |     send_error_details.result = GCMClient::TTL_EXCEEDED; | 
 |     delegate_->OnMessageSendError(app_id, send_error_details); | 
 |   } else if (status == MCSClient::SENT) { | 
 |     delegate_->OnSendAcknowledged(app_id, message_id); | 
 |   } else { | 
 |     delegate_->OnSendFinished(app_id, message_id, ToGCMClientResult(status)); | 
 |   } | 
 | } | 
 |  | 
 | void GCMClientImpl::OnMCSError() { | 
 |   // TODO(fgorski): For now it replaces the initialization method. Long term it | 
 |   // should have an error or status passed in. | 
 | } | 
 |  | 
 | void GCMClientImpl::HandleIncomingMessage(const gcm::MCSMessage& message) { | 
 |   DCHECK(delegate_); | 
 |  | 
 |   const mcs_proto::DataMessageStanza& data_message_stanza = | 
 |       reinterpret_cast<const mcs_proto::DataMessageStanza&>( | 
 |           message.GetProtobuf()); | 
 |   DCHECK_EQ(data_message_stanza.device_user_id(), kDefaultUserSerialNumber); | 
 |  | 
 |   // Copy all the data from the stanza to a MessageData object. When present, | 
 |   // keys like kSubtypeKey, kMessageTypeKey or kSendErrorMessageIdKey will be | 
 |   // filtered out later. | 
 |   MessageData message_data; | 
 |   for (int i = 0; i < data_message_stanza.app_data_size(); ++i) { | 
 |     std::string key = data_message_stanza.app_data(i).key(); | 
 |     message_data[key] = data_message_stanza.app_data(i).value(); | 
 |   } | 
 |  | 
 |   std::string subtype; | 
 |   auto subtype_iter = message_data.find(kSubtypeKey); | 
 |   if (subtype_iter != message_data.end()) { | 
 |     subtype = subtype_iter->second; | 
 |     message_data.erase(subtype_iter); | 
 |   } | 
 |  | 
 |   // SECURITY NOTE: Subtypes received from GCM *cannot* be trusted for | 
 |   // registrations without a subtype (as the sender can pass any subtype they | 
 |   // want). They can however be trusted for registrations that are known to have | 
 |   // a subtype (as GCM overwrites anything passed by the sender). | 
 |   // | 
 |   // So a given Chrome profile always passes a fixed string called | 
 |   // |product_category_for_subtypes| (of the form "com.chrome.macosx") as the | 
 |   // category when registering with a subtype, and incoming subtypes are only | 
 |   // trusted for that category. | 
 |   // | 
 |   // TODO(johnme): Remove this check if GCM starts sending the subtype in a | 
 |   // field that's guaranteed to be trusted (b/18198485). | 
 |   // | 
 |   // (On Android, all registrations made by Chrome on behalf of third-party | 
 |   // apps/extensions/websites have always had a subtype, so such a check is not | 
 |   // necessary - or possible, since category is fixed to the true package name). | 
 |   bool subtype_is_trusted = data_message_stanza.category() == | 
 |                             chrome_build_info_.product_category_for_subtypes; | 
 |   bool use_subtype = subtype_is_trusted && !subtype.empty(); | 
 |   std::string app_id = use_subtype ? subtype : data_message_stanza.category(); | 
 |  | 
 |   MessageType message_type = DATA_MESSAGE; | 
 |   auto type_iter = message_data.find(kMessageTypeKey); | 
 |   if (type_iter != message_data.end()) { | 
 |     message_type = DecodeMessageType(type_iter->second); | 
 |     message_data.erase(type_iter); | 
 |   } | 
 |  | 
 |   switch (message_type) { | 
 |     case DATA_MESSAGE: | 
 |       HandleIncomingDataMessage(app_id, use_subtype, data_message_stanza, | 
 |                                 message_data); | 
 |       break; | 
 |     case DELETED_MESSAGES: | 
 |       HandleIncomingDeletedMessages(app_id, data_message_stanza, message_data); | 
 |       break; | 
 |     case SEND_ERROR: | 
 |       HandleIncomingSendError(app_id, data_message_stanza, message_data); | 
 |       break; | 
 |     case UNKNOWN: | 
 |       DVLOG(1) << "Unknown message_type received. Message ignored. " | 
 |                << "App ID: " << app_id << "."; | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | void GCMClientImpl::HandleIncomingDataMessage( | 
 |     const std::string& app_id, | 
 |     bool was_subtype, | 
 |     const mcs_proto::DataMessageStanza& data_message_stanza, | 
 |     MessageData& message_data) { | 
 |   UMA_HISTOGRAM_BOOLEAN("GCM.DataMessageReceived", true); | 
 |  | 
 |   bool has_collapse_key = | 
 |       data_message_stanza.has_token() && !data_message_stanza.token().empty(); | 
 |   UMA_HISTOGRAM_BOOLEAN("GCM.DataMessageReceivedHasCollapseKey", | 
 |                         has_collapse_key); | 
 |  | 
 |   recorder_.RecordDataMessageReceived(app_id, data_message_stanza.from(), | 
 |                                       data_message_stanza.ByteSize(), | 
 |                                       GCMStatsRecorder::DATA_MESSAGE); | 
 |  | 
 |   IncomingMessage incoming_message; | 
 |   incoming_message.sender_id = data_message_stanza.from(); | 
 |   if (data_message_stanza.has_token()) | 
 |     incoming_message.collapse_key = data_message_stanza.token(); | 
 |   incoming_message.data = message_data; | 
 |   incoming_message.raw_data = data_message_stanza.raw_data(); | 
 |  | 
 |   delegate_->OnMessageReceived(app_id, incoming_message); | 
 | } | 
 |  | 
 | void GCMClientImpl::HandleIncomingDeletedMessages( | 
 |     const std::string& app_id, | 
 |     const mcs_proto::DataMessageStanza& data_message_stanza, | 
 |     MessageData& message_data) { | 
 |   int deleted_count = 0; | 
 |   auto count_iter = message_data.find(kDeletedCountKey); | 
 |   if (count_iter != message_data.end()) { | 
 |     if (!base::StringToInt(count_iter->second, &deleted_count)) | 
 |       deleted_count = 0; | 
 |   } | 
 |   UMA_HISTOGRAM_COUNTS_1000("GCM.DeletedMessagesReceived", deleted_count); | 
 |  | 
 |   recorder_.RecordDataMessageReceived(app_id, data_message_stanza.from(), | 
 |                                       data_message_stanza.ByteSize(), | 
 |                                       GCMStatsRecorder::DELETED_MESSAGES); | 
 |   delegate_->OnMessagesDeleted(app_id); | 
 | } | 
 |  | 
 | void GCMClientImpl::HandleIncomingSendError( | 
 |     const std::string& app_id, | 
 |     const mcs_proto::DataMessageStanza& data_message_stanza, | 
 |     MessageData& message_data) { | 
 |   SendErrorDetails send_error_details; | 
 |   send_error_details.additional_data = message_data; | 
 |   send_error_details.result = SERVER_ERROR; | 
 |  | 
 |   auto iter = send_error_details.additional_data.find(kSendErrorMessageIdKey); | 
 |   if (iter != send_error_details.additional_data.end()) { | 
 |     send_error_details.message_id = iter->second; | 
 |     send_error_details.additional_data.erase(iter); | 
 |   } | 
 |  | 
 |   recorder_.RecordIncomingSendError(app_id, data_message_stanza.to(), | 
 |                                     data_message_stanza.id()); | 
 |   delegate_->OnMessageSendError(app_id, send_error_details); | 
 | } | 
 |  | 
 | bool GCMClientImpl::HasStandaloneRegisteredApp() const { | 
 |   if (registrations_.empty()) | 
 |     return false; | 
 |   // Note that account mapper is not counted as a standalone app since it is | 
 |   // automatically started when other app uses GCM. | 
 |   return registrations_.size() > 1 || | 
 |          !ExistsGCMRegistrationInMap(registrations_, kGCMAccountMapperAppId); | 
 | } | 
 |  | 
 | }  // namespace gcm |