[GCM] Passing GCMClient::AccountTokenInfo list to GCMDriver
[chromium-blink-merge.git] / components / gcm_driver / gcm_account_mapper.cc
blob046abcd3918554b99c316ee0d9266d5549884704
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/gcm_driver/gcm_account_mapper.h"
7 #include "base/bind.h"
8 #include "base/guid.h"
9 #include "base/time/clock.h"
10 #include "base/time/default_clock.h"
11 #include "components/gcm_driver/gcm_driver_desktop.h"
12 #include "google_apis/gcm/engine/gcm_store.h"
14 namespace gcm {
16 namespace {
18 const char kGCMAccountMapperSenderId[] = "745476177629";
19 const char kGCMAccountMapperAppId[] = "com.google.android.gms";
20 const int kGCMAddMappingMessageTTL = 30 * 60; // 0.5 hours in seconds.
21 const int kGCMRemoveMappingMessageTTL = 24 * 60 * 60; // 1 day in seconds.
22 const int kGCMUpdateIntervalHours = 24;
23 // Because adding an account mapping dependents on a fresh OAuth2 token, we
24 // allow the update to happen earlier than update due time, if it is within
25 // the early start time to take advantage of that token.
26 const int kGCMUpdateEarlyStartHours = 6;
27 const char kRegistrationIdMessgaeKey[] = "id";
28 const char kTokenMessageKey[] = "t";
29 const char kAccountMessageKey[] = "a";
30 const char kRemoveAccountKey[] = "r";
31 const char kRemoveAccountValue[] = "1";
33 std::string GenerateMessageID() {
34 return base::GenerateGUID();
37 } // namespace
39 GCMAccountMapper::GCMAccountMapper(GCMDriver* gcm_driver)
40 : gcm_driver_(gcm_driver),
41 clock_(new base::DefaultClock),
42 initialized_(false),
43 weak_ptr_factory_(this) {
46 GCMAccountMapper::~GCMAccountMapper() {
49 void GCMAccountMapper::Initialize(
50 const std::vector<AccountMapping>& account_mappings) {
51 DCHECK(!initialized_);
52 initialized_ = true;
53 accounts_ = account_mappings;
54 gcm_driver_->AddAppHandler(kGCMAccountMapperAppId, this);
55 GetRegistration();
58 void GCMAccountMapper::SetAccountTokens(
59 const std::vector<GCMClient::AccountTokenInfo>& account_tokens) {
60 // If account mapper is not ready to handle tasks yet, save the latest
61 // account tokens and return.
62 if (!IsReady()) {
63 pending_account_tokens_ = account_tokens;
64 // If mapper is initialized, but still does not have registration ID,
65 // maybe the registration gave up. Retrying in case.
66 if (initialized_)
67 GetRegistration();
68 return;
71 // Start from removing the old tokens, from all of the known accounts.
72 for (AccountMappings::iterator iter = accounts_.begin();
73 iter != accounts_.end();
74 ++iter) {
75 iter->access_token.clear();
78 // Update the internal collection of mappings with the new tokens.
79 for (std::vector<GCMClient::AccountTokenInfo>::const_iterator token_iter =
80 account_tokens.begin();
81 token_iter != account_tokens.end();
82 ++token_iter) {
83 AccountMapping* account_mapping =
84 FindMappingByAccountId(token_iter->account_id);
85 if (!account_mapping) {
86 AccountMapping new_mapping;
87 new_mapping.status = AccountMapping::NEW;
88 new_mapping.account_id = token_iter->account_id;
89 new_mapping.access_token = token_iter->access_token;
90 new_mapping.email = token_iter->email;
91 accounts_.push_back(new_mapping);
92 } else {
93 // Since we got a token for an account, drop the remove message and treat
94 // it as mapped.
95 if (account_mapping->status == AccountMapping::REMOVING) {
96 account_mapping->status = AccountMapping::MAPPED;
97 account_mapping->status_change_timestamp = base::Time();
98 account_mapping->last_message_id.clear();
101 account_mapping->email = token_iter->email;
102 account_mapping->access_token = token_iter->access_token;
106 // Decide what to do with each account (either start mapping, or start
107 // removing).
108 for (AccountMappings::iterator mappings_iter = accounts_.begin();
109 mappings_iter != accounts_.end();
110 ++mappings_iter) {
111 if (mappings_iter->access_token.empty()) {
112 // Send a remove message if the account was not previously being removed,
113 // or it doesn't have a pending message, or the pending message is
114 // already expired, but OnSendError event was lost.
115 if (mappings_iter->status != AccountMapping::REMOVING ||
116 mappings_iter->last_message_id.empty() ||
117 IsLastStatusChangeOlderThanTTL(*mappings_iter)) {
118 SendRemoveMappingMessage(*mappings_iter);
120 } else {
121 // A message is sent for all of the mappings considered NEW, or mappings
122 // that are ADDING, but have expired message (OnSendError event lost), or
123 // for those mapped accounts that can be refreshed.
124 if (mappings_iter->status == AccountMapping::NEW ||
125 (mappings_iter->status == AccountMapping::ADDING &&
126 IsLastStatusChangeOlderThanTTL(*mappings_iter)) ||
127 (mappings_iter->status == AccountMapping::MAPPED &&
128 CanTriggerUpdate(mappings_iter->status_change_timestamp))) {
129 mappings_iter->last_message_id.clear();
130 SendAddMappingMessage(*mappings_iter);
136 void GCMAccountMapper::ShutdownHandler() {
137 gcm_driver_->RemoveAppHandler(kGCMAccountMapperAppId);
140 void GCMAccountMapper::OnMessage(const std::string& app_id,
141 const GCMClient::IncomingMessage& message) {
142 // Account message does not expect messages right now.
145 void GCMAccountMapper::OnMessagesDeleted(const std::string& app_id) {
146 // Account message does not expect messages right now.
149 void GCMAccountMapper::OnSendError(
150 const std::string& app_id,
151 const GCMClient::SendErrorDetails& send_error_details) {
152 DCHECK_EQ(app_id, kGCMAccountMapperAppId);
154 AccountMappings::iterator account_mapping_it =
155 FindMappingByMessageId(send_error_details.message_id);
157 if (account_mapping_it == accounts_.end())
158 return;
160 if (send_error_details.result != GCMClient::TTL_EXCEEDED) {
161 DVLOG(1) << "Send error result different than TTL EXCEEDED: "
162 << send_error_details.result << ". "
163 << "Postponing the retry until a new batch of tokens arrives.";
164 return;
167 if (account_mapping_it->status == AccountMapping::REMOVING) {
168 // Another message to remove mapping can be sent immediately, because TTL
169 // for those is one day. No need to back off.
170 SendRemoveMappingMessage(*account_mapping_it);
171 } else {
172 if (account_mapping_it->status == AccountMapping::ADDING) {
173 // There is no mapping established, so we can remove the entry.
174 // Getting a fresh token will trigger a new attempt.
175 gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
176 accounts_.erase(account_mapping_it);
177 } else {
178 // Account is already MAPPED, we have to wait for another token.
179 account_mapping_it->last_message_id.clear();
180 gcm_driver_->UpdateAccountMapping(*account_mapping_it);
185 void GCMAccountMapper::OnSendAcknowledged(const std::string& app_id,
186 const std::string& message_id) {
187 DCHECK_EQ(app_id, kGCMAccountMapperAppId);
188 AccountMappings::iterator account_mapping_it =
189 FindMappingByMessageId(message_id);
191 DVLOG(1) << "OnSendAcknowledged with message ID: " << message_id;
193 if (account_mapping_it == accounts_.end())
194 return;
196 // Here is where we advance a status of a mapping and persist or remove.
197 if (account_mapping_it->status == AccountMapping::REMOVING) {
198 // Message removing the account has been confirmed by the GCM, we can remove
199 // all the information related to the account (from memory and store).
200 gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
201 accounts_.erase(account_mapping_it);
202 } else {
203 // Mapping status is ADDING only when it is a first time mapping.
204 DCHECK(account_mapping_it->status == AccountMapping::ADDING ||
205 account_mapping_it->status == AccountMapping::MAPPED);
207 // Account is marked as mapped with the current time.
208 account_mapping_it->status = AccountMapping::MAPPED;
209 account_mapping_it->status_change_timestamp = clock_->Now();
210 // There is no pending message for the account.
211 account_mapping_it->last_message_id.clear();
213 gcm_driver_->UpdateAccountMapping(*account_mapping_it);
217 bool GCMAccountMapper::CanHandle(const std::string& app_id) const {
218 return app_id.compare(kGCMAccountMapperAppId) == 0;
221 bool GCMAccountMapper::IsReady() {
222 return initialized_ && !registration_id_.empty();
225 void GCMAccountMapper::SendAddMappingMessage(AccountMapping& account_mapping) {
226 CreateAndSendMessage(account_mapping);
229 void GCMAccountMapper::SendRemoveMappingMessage(
230 AccountMapping& account_mapping) {
231 // We want to persist an account that is being removed as quickly as possible
232 // as well as clean up the last message information.
233 if (account_mapping.status != AccountMapping::REMOVING) {
234 account_mapping.status = AccountMapping::REMOVING;
235 account_mapping.status_change_timestamp = clock_->Now();
238 account_mapping.last_message_id.clear();
240 gcm_driver_->UpdateAccountMapping(account_mapping);
242 CreateAndSendMessage(account_mapping);
245 void GCMAccountMapper::CreateAndSendMessage(
246 const AccountMapping& account_mapping) {
247 GCMClient::OutgoingMessage outgoing_message;
248 outgoing_message.id = GenerateMessageID();
249 outgoing_message.data[kRegistrationIdMessgaeKey] = registration_id_;
250 outgoing_message.data[kAccountMessageKey] = account_mapping.email;
252 if (account_mapping.status == AccountMapping::REMOVING) {
253 outgoing_message.time_to_live = kGCMRemoveMappingMessageTTL;
254 outgoing_message.data[kRemoveAccountKey] = kRemoveAccountValue;
255 } else {
256 outgoing_message.data[kTokenMessageKey] = account_mapping.access_token;
257 outgoing_message.time_to_live = kGCMAddMappingMessageTTL;
260 gcm_driver_->Send(kGCMAccountMapperAppId,
261 kGCMAccountMapperSenderId,
262 outgoing_message,
263 base::Bind(&GCMAccountMapper::OnSendFinished,
264 weak_ptr_factory_.GetWeakPtr(),
265 account_mapping.account_id));
268 void GCMAccountMapper::OnSendFinished(const std::string& account_id,
269 const std::string& message_id,
270 GCMClient::Result result) {
271 // TODO(fgorski): Add another attempt, in case the QUEUE is not full.
272 if (result != GCMClient::SUCCESS)
273 return;
275 AccountMapping* account_mapping = FindMappingByAccountId(account_id);
276 DCHECK(account_mapping);
278 // If we are dealing with account with status NEW, it is the first time
279 // mapping, and we should mark it as ADDING.
280 if (account_mapping->status == AccountMapping::NEW) {
281 account_mapping->status = AccountMapping::ADDING;
282 account_mapping->status_change_timestamp = clock_->Now();
285 account_mapping->last_message_id = message_id;
287 gcm_driver_->UpdateAccountMapping(*account_mapping);
290 void GCMAccountMapper::GetRegistration() {
291 DCHECK(registration_id_.empty());
292 std::vector<std::string> sender_ids;
293 sender_ids.push_back(kGCMAccountMapperSenderId);
294 gcm_driver_->Register(kGCMAccountMapperAppId,
295 sender_ids,
296 base::Bind(&GCMAccountMapper::OnRegisterFinished,
297 weak_ptr_factory_.GetWeakPtr()));
300 void GCMAccountMapper::OnRegisterFinished(const std::string& registration_id,
301 GCMClient::Result result) {
302 if (result == GCMClient::SUCCESS)
303 registration_id_ = registration_id;
305 if (IsReady()) {
306 if (!pending_account_tokens_.empty()) {
307 SetAccountTokens(pending_account_tokens_);
308 pending_account_tokens_.clear();
313 bool GCMAccountMapper::CanTriggerUpdate(
314 const base::Time& last_update_time) const {
315 return last_update_time +
316 base::TimeDelta::FromHours(kGCMUpdateIntervalHours -
317 kGCMUpdateEarlyStartHours) <
318 clock_->Now();
321 bool GCMAccountMapper::IsLastStatusChangeOlderThanTTL(
322 const AccountMapping& account_mapping) const {
323 int ttl_seconds = account_mapping.status == AccountMapping::REMOVING ?
324 kGCMRemoveMappingMessageTTL : kGCMAddMappingMessageTTL;
325 return account_mapping.status_change_timestamp +
326 base::TimeDelta::FromSeconds(ttl_seconds) < clock_->Now();
329 AccountMapping* GCMAccountMapper::FindMappingByAccountId(
330 const std::string& account_id) {
331 for (AccountMappings::iterator iter = accounts_.begin();
332 iter != accounts_.end();
333 ++iter) {
334 if (iter->account_id == account_id)
335 return &*iter;
338 return NULL;
341 GCMAccountMapper::AccountMappings::iterator
342 GCMAccountMapper::FindMappingByMessageId(const std::string& message_id) {
343 for (std::vector<AccountMapping>::iterator iter = accounts_.begin();
344 iter != accounts_.end();
345 ++iter) {
346 if (iter->last_message_id == message_id)
347 return iter;
350 return accounts_.end();
353 void GCMAccountMapper::SetClockForTesting(scoped_ptr<base::Clock> clock) {
354 clock_ = clock.Pass();
357 } // namespace gcm