Explicitly add python-numpy dependency to install-build-deps.
[chromium-blink-merge.git] / components / gcm_driver / gcm_account_mapper.cc
blobfc84369349ac4f3e27bf44796104a0a72da6ada8
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 int kGCMAddMappingMessageTTL = 30 * 60; // 0.5 hours in seconds.
20 const int kGCMRemoveMappingMessageTTL = 24 * 60 * 60; // 1 day in seconds.
21 const int kGCMUpdateIntervalHours = 24;
22 // Because adding an account mapping dependents on a fresh OAuth2 token, we
23 // allow the update to happen earlier than update due time, if it is within
24 // the early start time to take advantage of that token.
25 const int kGCMUpdateEarlyStartHours = 6;
26 const char kRegistrationIdMessgaeKey[] = "id";
27 const char kTokenMessageKey[] = "t";
28 const char kAccountMessageKey[] = "a";
29 const char kRemoveAccountKey[] = "r";
30 const char kRemoveAccountValue[] = "1";
32 std::string GenerateMessageID() {
33 return base::GenerateGUID();
36 } // namespace
38 const char kGCMAccountMapperAppId[] = "com.google.android.gms";
40 GCMAccountMapper::GCMAccountMapper(GCMDriver* gcm_driver)
41 : gcm_driver_(gcm_driver),
42 clock_(new base::DefaultClock),
43 initialized_(false),
44 weak_ptr_factory_(this) {
47 GCMAccountMapper::~GCMAccountMapper() {
50 void GCMAccountMapper::Initialize(
51 const std::vector<AccountMapping>& account_mappings) {
52 DCHECK(!initialized_);
53 initialized_ = true;
54 accounts_ = account_mappings;
55 GetRegistration();
58 void GCMAccountMapper::SetAccountTokens(
59 const std::vector<GCMClient::AccountTokenInfo>& account_tokens) {
60 DVLOG(1) << "GCMAccountMapper::SetAccountTokens called with "
61 << account_tokens.size() << " accounts.";
63 // If account mapper is not ready to handle tasks yet, save the latest
64 // account tokens and return.
65 if (!IsReady()) {
66 pending_account_tokens_ = account_tokens;
67 // If mapper is initialized, but still does not have registration ID,
68 // maybe the registration gave up. Retrying in case.
69 if (initialized_ && gcm_driver_->IsStarted())
70 GetRegistration();
71 return;
74 // Start from removing the old tokens, from all of the known accounts.
75 for (AccountMappings::iterator iter = accounts_.begin();
76 iter != accounts_.end();
77 ++iter) {
78 iter->access_token.clear();
81 // Update the internal collection of mappings with the new tokens.
82 for (std::vector<GCMClient::AccountTokenInfo>::const_iterator token_iter =
83 account_tokens.begin();
84 token_iter != account_tokens.end();
85 ++token_iter) {
86 AccountMapping* account_mapping =
87 FindMappingByAccountId(token_iter->account_id);
88 if (!account_mapping) {
89 AccountMapping new_mapping;
90 new_mapping.status = AccountMapping::NEW;
91 new_mapping.account_id = token_iter->account_id;
92 new_mapping.access_token = token_iter->access_token;
93 new_mapping.email = token_iter->email;
94 accounts_.push_back(new_mapping);
95 } else {
96 // Since we got a token for an account, drop the remove message and treat
97 // it as mapped.
98 if (account_mapping->status == AccountMapping::REMOVING) {
99 account_mapping->status = AccountMapping::MAPPED;
100 account_mapping->status_change_timestamp = base::Time();
101 account_mapping->last_message_id.clear();
104 account_mapping->email = token_iter->email;
105 account_mapping->access_token = token_iter->access_token;
109 // Decide what to do with each account (either start mapping, or start
110 // removing).
111 for (AccountMappings::iterator mappings_iter = accounts_.begin();
112 mappings_iter != accounts_.end();
113 ++mappings_iter) {
114 if (mappings_iter->access_token.empty()) {
115 // Send a remove message if the account was not previously being removed,
116 // or it doesn't have a pending message, or the pending message is
117 // already expired, but OnSendError event was lost.
118 if (mappings_iter->status != AccountMapping::REMOVING ||
119 mappings_iter->last_message_id.empty() ||
120 IsLastStatusChangeOlderThanTTL(*mappings_iter)) {
121 SendRemoveMappingMessage(*mappings_iter);
123 } else {
124 // A message is sent for all of the mappings considered NEW, or mappings
125 // that are ADDING, but have expired message (OnSendError event lost), or
126 // for those mapped accounts that can be refreshed.
127 if (mappings_iter->status == AccountMapping::NEW ||
128 (mappings_iter->status == AccountMapping::ADDING &&
129 IsLastStatusChangeOlderThanTTL(*mappings_iter)) ||
130 (mappings_iter->status == AccountMapping::MAPPED &&
131 CanTriggerUpdate(mappings_iter->status_change_timestamp))) {
132 mappings_iter->last_message_id.clear();
133 SendAddMappingMessage(*mappings_iter);
139 void GCMAccountMapper::ShutdownHandler() {
140 initialized_ = false;
141 accounts_.clear();
142 registration_id_.clear();
145 void GCMAccountMapper::OnMessage(const std::string& app_id,
146 const GCMClient::IncomingMessage& message) {
147 // Account message does not expect messages right now.
150 void GCMAccountMapper::OnMessagesDeleted(const std::string& app_id) {
151 // Account message does not expect messages right now.
154 void GCMAccountMapper::OnSendError(
155 const std::string& app_id,
156 const GCMClient::SendErrorDetails& send_error_details) {
157 DCHECK_EQ(app_id, kGCMAccountMapperAppId);
159 AccountMappings::iterator account_mapping_it =
160 FindMappingByMessageId(send_error_details.message_id);
162 if (account_mapping_it == accounts_.end())
163 return;
165 if (send_error_details.result != GCMClient::TTL_EXCEEDED) {
166 DVLOG(1) << "Send error result different than TTL EXCEEDED: "
167 << send_error_details.result << ". "
168 << "Postponing the retry until a new batch of tokens arrives.";
169 return;
172 if (account_mapping_it->status == AccountMapping::REMOVING) {
173 // Another message to remove mapping can be sent immediately, because TTL
174 // for those is one day. No need to back off.
175 SendRemoveMappingMessage(*account_mapping_it);
176 } else {
177 if (account_mapping_it->status == AccountMapping::ADDING) {
178 // There is no mapping established, so we can remove the entry.
179 // Getting a fresh token will trigger a new attempt.
180 gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
181 accounts_.erase(account_mapping_it);
182 } else {
183 // Account is already MAPPED, we have to wait for another token.
184 account_mapping_it->last_message_id.clear();
185 gcm_driver_->UpdateAccountMapping(*account_mapping_it);
190 void GCMAccountMapper::OnSendAcknowledged(const std::string& app_id,
191 const std::string& message_id) {
192 DCHECK_EQ(app_id, kGCMAccountMapperAppId);
193 AccountMappings::iterator account_mapping_it =
194 FindMappingByMessageId(message_id);
196 DVLOG(1) << "OnSendAcknowledged with message ID: " << message_id;
198 if (account_mapping_it == accounts_.end())
199 return;
201 // Here is where we advance a status of a mapping and persist or remove.
202 if (account_mapping_it->status == AccountMapping::REMOVING) {
203 // Message removing the account has been confirmed by the GCM, we can remove
204 // all the information related to the account (from memory and store).
205 gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
206 accounts_.erase(account_mapping_it);
207 } else {
208 // Mapping status is ADDING only when it is a first time mapping.
209 DCHECK(account_mapping_it->status == AccountMapping::ADDING ||
210 account_mapping_it->status == AccountMapping::MAPPED);
212 // Account is marked as mapped with the current time.
213 account_mapping_it->status = AccountMapping::MAPPED;
214 account_mapping_it->status_change_timestamp = clock_->Now();
215 // There is no pending message for the account.
216 account_mapping_it->last_message_id.clear();
218 gcm_driver_->UpdateAccountMapping(*account_mapping_it);
222 bool GCMAccountMapper::CanHandle(const std::string& app_id) const {
223 return app_id.compare(kGCMAccountMapperAppId) == 0;
226 bool GCMAccountMapper::IsReady() {
227 return initialized_ && gcm_driver_->IsStarted() && !registration_id_.empty();
230 void GCMAccountMapper::SendAddMappingMessage(AccountMapping& account_mapping) {
231 CreateAndSendMessage(account_mapping);
234 void GCMAccountMapper::SendRemoveMappingMessage(
235 AccountMapping& account_mapping) {
236 // We want to persist an account that is being removed as quickly as possible
237 // as well as clean up the last message information.
238 if (account_mapping.status != AccountMapping::REMOVING) {
239 account_mapping.status = AccountMapping::REMOVING;
240 account_mapping.status_change_timestamp = clock_->Now();
243 account_mapping.last_message_id.clear();
245 gcm_driver_->UpdateAccountMapping(account_mapping);
247 CreateAndSendMessage(account_mapping);
250 void GCMAccountMapper::CreateAndSendMessage(
251 const AccountMapping& account_mapping) {
252 GCMClient::OutgoingMessage outgoing_message;
253 outgoing_message.id = GenerateMessageID();
254 outgoing_message.data[kRegistrationIdMessgaeKey] = registration_id_;
255 outgoing_message.data[kAccountMessageKey] = account_mapping.email;
257 if (account_mapping.status == AccountMapping::REMOVING) {
258 outgoing_message.time_to_live = kGCMRemoveMappingMessageTTL;
259 outgoing_message.data[kRemoveAccountKey] = kRemoveAccountValue;
260 } else {
261 outgoing_message.data[kTokenMessageKey] = account_mapping.access_token;
262 outgoing_message.time_to_live = kGCMAddMappingMessageTTL;
265 gcm_driver_->Send(kGCMAccountMapperAppId,
266 kGCMAccountMapperSenderId,
267 outgoing_message,
268 base::Bind(&GCMAccountMapper::OnSendFinished,
269 weak_ptr_factory_.GetWeakPtr(),
270 account_mapping.account_id));
273 void GCMAccountMapper::OnSendFinished(const std::string& account_id,
274 const std::string& message_id,
275 GCMClient::Result result) {
276 // TODO(fgorski): Add another attempt, in case the QUEUE is not full.
277 if (result != GCMClient::SUCCESS)
278 return;
280 AccountMapping* account_mapping = FindMappingByAccountId(account_id);
281 DCHECK(account_mapping);
283 // If we are dealing with account with status NEW, it is the first time
284 // mapping, and we should mark it as ADDING.
285 if (account_mapping->status == AccountMapping::NEW) {
286 account_mapping->status = AccountMapping::ADDING;
287 account_mapping->status_change_timestamp = clock_->Now();
290 account_mapping->last_message_id = message_id;
292 gcm_driver_->UpdateAccountMapping(*account_mapping);
295 void GCMAccountMapper::GetRegistration() {
296 DCHECK(registration_id_.empty());
297 std::vector<std::string> sender_ids;
298 sender_ids.push_back(kGCMAccountMapperSenderId);
299 gcm_driver_->Register(kGCMAccountMapperAppId,
300 sender_ids,
301 base::Bind(&GCMAccountMapper::OnRegisterFinished,
302 weak_ptr_factory_.GetWeakPtr()));
305 void GCMAccountMapper::OnRegisterFinished(const std::string& registration_id,
306 GCMClient::Result result) {
307 if (result == GCMClient::SUCCESS)
308 registration_id_ = registration_id;
310 if (IsReady()) {
311 if (!pending_account_tokens_.empty()) {
312 SetAccountTokens(pending_account_tokens_);
313 pending_account_tokens_.clear();
318 bool GCMAccountMapper::CanTriggerUpdate(
319 const base::Time& last_update_time) const {
320 return last_update_time +
321 base::TimeDelta::FromHours(kGCMUpdateIntervalHours -
322 kGCMUpdateEarlyStartHours) <
323 clock_->Now();
326 bool GCMAccountMapper::IsLastStatusChangeOlderThanTTL(
327 const AccountMapping& account_mapping) const {
328 int ttl_seconds = account_mapping.status == AccountMapping::REMOVING ?
329 kGCMRemoveMappingMessageTTL : kGCMAddMappingMessageTTL;
330 return account_mapping.status_change_timestamp +
331 base::TimeDelta::FromSeconds(ttl_seconds) < clock_->Now();
334 AccountMapping* GCMAccountMapper::FindMappingByAccountId(
335 const std::string& account_id) {
336 for (AccountMappings::iterator iter = accounts_.begin();
337 iter != accounts_.end();
338 ++iter) {
339 if (iter->account_id == account_id)
340 return &*iter;
343 return NULL;
346 GCMAccountMapper::AccountMappings::iterator
347 GCMAccountMapper::FindMappingByMessageId(const std::string& message_id) {
348 for (std::vector<AccountMapping>::iterator iter = accounts_.begin();
349 iter != accounts_.end();
350 ++iter) {
351 if (iter->last_message_id == message_id)
352 return iter;
355 return accounts_.end();
358 void GCMAccountMapper::SetClockForTesting(scoped_ptr<base::Clock> clock) {
359 clock_ = clock.Pass();
362 } // namespace gcm