OLD | NEW |
(Empty) | |
| 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_account_mapper.h" |
| 6 |
| 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" |
| 13 |
| 14 namespace gcm { |
| 15 |
| 16 namespace { |
| 17 |
| 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"; |
| 32 |
| 33 std::string GenerateMessageID() { |
| 34 return base::GenerateGUID(); |
| 35 } |
| 36 |
| 37 } // namespace |
| 38 |
| 39 GCMAccountMapper::GCMAccountMapper(GCMDriver* gcm_driver) |
| 40 : gcm_driver_(gcm_driver), |
| 41 clock_(new base::DefaultClock), |
| 42 initialized_(false), |
| 43 weak_ptr_factory_(this) { |
| 44 } |
| 45 |
| 46 GCMAccountMapper::~GCMAccountMapper() { |
| 47 } |
| 48 |
| 49 void GCMAccountMapper::Initialize( |
| 50 const std::vector<AccountMapping>& account_mappings, |
| 51 const std::string& registration_id) { |
| 52 DCHECK(!initialized_); |
| 53 initialized_ = true; |
| 54 registration_id_ = registration_id; |
| 55 |
| 56 accounts_ = account_mappings; |
| 57 |
| 58 gcm_driver_->AddAppHandler(kGCMAccountMapperAppId, this); |
| 59 |
| 60 // TODO(fgorski): if no registration ID, get registration ID. |
| 61 } |
| 62 |
| 63 void GCMAccountMapper::SetAccountTokens( |
| 64 const std::vector<GCMClient::AccountTokenInfo> account_tokens) { |
| 65 DCHECK(initialized_); |
| 66 |
| 67 // Start from removing the old tokens, from all of the known accounts. |
| 68 for (AccountMappings::iterator iter = accounts_.begin(); |
| 69 iter != accounts_.end(); |
| 70 ++iter) { |
| 71 iter->access_token.clear(); |
| 72 } |
| 73 |
| 74 // Update the internal collection of mappings with the new tokens. |
| 75 for (std::vector<GCMClient::AccountTokenInfo>::const_iterator token_iter = |
| 76 account_tokens.begin(); |
| 77 token_iter != account_tokens.end(); |
| 78 ++token_iter) { |
| 79 AccountMapping* account_mapping = |
| 80 FindMappingByAccountId(token_iter->account_id); |
| 81 if (!account_mapping) { |
| 82 AccountMapping new_mapping; |
| 83 new_mapping.status = AccountMapping::NEW; |
| 84 new_mapping.account_id = token_iter->account_id; |
| 85 new_mapping.access_token = token_iter->access_token; |
| 86 new_mapping.email = token_iter->email; |
| 87 accounts_.push_back(new_mapping); |
| 88 } else { |
| 89 // Since we got a token for an account, drop the remove message and treat |
| 90 // it as mapped. |
| 91 if (account_mapping->status == AccountMapping::REMOVING) { |
| 92 account_mapping->status = AccountMapping::MAPPED; |
| 93 account_mapping->status_change_timestamp = base::Time(); |
| 94 account_mapping->last_message_id.clear(); |
| 95 } |
| 96 |
| 97 account_mapping->email = token_iter->email; |
| 98 account_mapping->access_token = token_iter->access_token; |
| 99 } |
| 100 } |
| 101 |
| 102 // Decide what to do with each account (either start mapping, or start |
| 103 // removing). |
| 104 for (AccountMappings::iterator mappings_iter = accounts_.begin(); |
| 105 mappings_iter != accounts_.end(); |
| 106 ++mappings_iter) { |
| 107 if (mappings_iter->access_token.empty()) { |
| 108 // Send a remove message if the account was not previously being removed, |
| 109 // or it doesn't have a pending message, or the pending message is |
| 110 // already expired, but OnSendError event was lost. |
| 111 if (mappings_iter->status != AccountMapping::REMOVING || |
| 112 mappings_iter->last_message_id.empty() || |
| 113 IsLastStatusChangeOlderThanTTL(*mappings_iter)) { |
| 114 SendRemoveMappingMessage(*mappings_iter); |
| 115 } |
| 116 } else { |
| 117 // A message is sent for all of the mappings considered NEW, or mappings |
| 118 // that are ADDING, but have expired message (OnSendError event lost), or |
| 119 // for those mapped accounts that can be refreshed. |
| 120 if (mappings_iter->status == AccountMapping::NEW || |
| 121 (mappings_iter->status == AccountMapping::ADDING && |
| 122 IsLastStatusChangeOlderThanTTL(*mappings_iter)) || |
| 123 (mappings_iter->status == AccountMapping::MAPPED && |
| 124 CanTriggerUpdate(mappings_iter->status_change_timestamp))) { |
| 125 mappings_iter->last_message_id.clear(); |
| 126 SendAddMappingMessage(*mappings_iter); |
| 127 } |
| 128 } |
| 129 } |
| 130 } |
| 131 |
| 132 void GCMAccountMapper::ShutdownHandler() { |
| 133 gcm_driver_->RemoveAppHandler(kGCMAccountMapperAppId); |
| 134 } |
| 135 |
| 136 void GCMAccountMapper::OnMessage(const std::string& app_id, |
| 137 const GCMClient::IncomingMessage& message) { |
| 138 // Account message does not expect messages right now. |
| 139 } |
| 140 |
| 141 void GCMAccountMapper::OnMessagesDeleted(const std::string& app_id) { |
| 142 // Account message does not expect messages right now. |
| 143 } |
| 144 |
| 145 void GCMAccountMapper::OnSendError( |
| 146 const std::string& app_id, |
| 147 const GCMClient::SendErrorDetails& send_error_details) { |
| 148 DCHECK_EQ(app_id, kGCMAccountMapperAppId); |
| 149 |
| 150 AccountMappings::iterator account_mapping_it = |
| 151 FindMappingByMessageId(send_error_details.message_id); |
| 152 |
| 153 if (account_mapping_it == accounts_.end()) |
| 154 return; |
| 155 |
| 156 if (send_error_details.result != GCMClient::TTL_EXCEEDED) { |
| 157 DVLOG(1) << "Send error result different than TTL EXCEEDED: " |
| 158 << send_error_details.result << ". " |
| 159 << "Postponing the retry until a new batch of tokens arrives."; |
| 160 return; |
| 161 } |
| 162 |
| 163 if (account_mapping_it->status == AccountMapping::REMOVING) { |
| 164 // Another message to remove mapping can be sent immediately, because TTL |
| 165 // for those is one day. No need to back off. |
| 166 SendRemoveMappingMessage(*account_mapping_it); |
| 167 } else { |
| 168 if (account_mapping_it->status == AccountMapping::ADDING) { |
| 169 // There is no mapping established, so we can remove the entry. |
| 170 // Getting a fresh token will trigger a new attempt. |
| 171 gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id); |
| 172 accounts_.erase(account_mapping_it); |
| 173 } else { |
| 174 // Account is already MAPPED, we have to wait for another token. |
| 175 account_mapping_it->last_message_id.clear(); |
| 176 gcm_driver_->UpdateAccountMapping(*account_mapping_it); |
| 177 } |
| 178 } |
| 179 } |
| 180 |
| 181 void GCMAccountMapper::OnSendAcknowledged(const std::string& app_id, |
| 182 const std::string& message_id) { |
| 183 DCHECK_EQ(app_id, kGCMAccountMapperAppId); |
| 184 AccountMappings::iterator account_mapping_it = |
| 185 FindMappingByMessageId(message_id); |
| 186 |
| 187 DVLOG(1) << "OnSendAcknowledged with message ID: " << message_id; |
| 188 |
| 189 if (account_mapping_it == accounts_.end()) |
| 190 return; |
| 191 |
| 192 // Here is where we advance a status of a mapping and persist or remove. |
| 193 if (account_mapping_it->status == AccountMapping::REMOVING) { |
| 194 // Message removing the account has been confirmed by the GCM, we can remove |
| 195 // all the information related to the account (from memory and store). |
| 196 gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id); |
| 197 accounts_.erase(account_mapping_it); |
| 198 } else { |
| 199 // Mapping status is ADDING only when it is a first time mapping. |
| 200 DCHECK(account_mapping_it->status == AccountMapping::ADDING || |
| 201 account_mapping_it->status == AccountMapping::MAPPED); |
| 202 |
| 203 // Account is marked as mapped with the current time. |
| 204 account_mapping_it->status = AccountMapping::MAPPED; |
| 205 account_mapping_it->status_change_timestamp = clock_->Now(); |
| 206 // There is no pending message for the account. |
| 207 account_mapping_it->last_message_id.clear(); |
| 208 |
| 209 gcm_driver_->UpdateAccountMapping(*account_mapping_it); |
| 210 } |
| 211 } |
| 212 |
| 213 bool GCMAccountMapper::CanHandle(const std::string& app_id) const { |
| 214 return app_id.compare(kGCMAccountMapperAppId) == 0; |
| 215 } |
| 216 |
| 217 void GCMAccountMapper::SendAddMappingMessage(AccountMapping& account_mapping) { |
| 218 CreateAndSendMessage(account_mapping); |
| 219 } |
| 220 |
| 221 void GCMAccountMapper::SendRemoveMappingMessage( |
| 222 AccountMapping& account_mapping) { |
| 223 // We want to persist an account that is being removed as quickly as possible |
| 224 // as well as clean up the last message information. |
| 225 if (account_mapping.status != AccountMapping::REMOVING) { |
| 226 account_mapping.status = AccountMapping::REMOVING; |
| 227 account_mapping.status_change_timestamp = clock_->Now(); |
| 228 } |
| 229 |
| 230 account_mapping.last_message_id.clear(); |
| 231 |
| 232 gcm_driver_->UpdateAccountMapping(account_mapping); |
| 233 |
| 234 CreateAndSendMessage(account_mapping); |
| 235 } |
| 236 |
| 237 void GCMAccountMapper::CreateAndSendMessage( |
| 238 const AccountMapping& account_mapping) { |
| 239 GCMClient::OutgoingMessage outgoing_message; |
| 240 outgoing_message.id = GenerateMessageID(); |
| 241 outgoing_message.data[kRegistrationIdMessgaeKey] = registration_id_; |
| 242 outgoing_message.data[kAccountMessageKey] = account_mapping.email; |
| 243 |
| 244 if (account_mapping.status == AccountMapping::REMOVING) { |
| 245 outgoing_message.time_to_live = kGCMRemoveMappingMessageTTL; |
| 246 outgoing_message.data[kRemoveAccountKey] = kRemoveAccountValue; |
| 247 } else { |
| 248 outgoing_message.data[kTokenMessageKey] = account_mapping.access_token; |
| 249 outgoing_message.time_to_live = kGCMAddMappingMessageTTL; |
| 250 } |
| 251 |
| 252 gcm_driver_->Send(kGCMAccountMapperAppId, |
| 253 kGCMAccountMapperSenderId, |
| 254 outgoing_message, |
| 255 base::Bind(&GCMAccountMapper::OnSendFinished, |
| 256 weak_ptr_factory_.GetWeakPtr(), |
| 257 account_mapping.account_id)); |
| 258 } |
| 259 |
| 260 |
| 261 void GCMAccountMapper::OnSendFinished(const std::string& account_id, |
| 262 const std::string& message_id, |
| 263 GCMClient::Result result) { |
| 264 // TODO(fgorski): Add another attempt, in case the QUEUE is not full. |
| 265 if (result != GCMClient::SUCCESS) |
| 266 return; |
| 267 |
| 268 AccountMapping* account_mapping = FindMappingByAccountId(account_id); |
| 269 DCHECK(account_mapping); |
| 270 |
| 271 // If we are dealing with account with status NEW, it is the first time |
| 272 // mapping, and we should mark it as ADDING. |
| 273 if (account_mapping->status == AccountMapping::NEW) { |
| 274 account_mapping->status = AccountMapping::ADDING; |
| 275 account_mapping->status_change_timestamp = clock_->Now(); |
| 276 } |
| 277 |
| 278 account_mapping->last_message_id = message_id; |
| 279 |
| 280 gcm_driver_->UpdateAccountMapping(*account_mapping); |
| 281 } |
| 282 |
| 283 bool GCMAccountMapper::CanTriggerUpdate( |
| 284 const base::Time& last_update_time) const { |
| 285 return last_update_time + |
| 286 base::TimeDelta::FromHours(kGCMUpdateIntervalHours - |
| 287 kGCMUpdateEarlyStartHours) < |
| 288 clock_->Now(); |
| 289 } |
| 290 |
| 291 bool GCMAccountMapper::IsLastStatusChangeOlderThanTTL( |
| 292 const AccountMapping& account_mapping) const { |
| 293 int ttl_seconds = account_mapping.status == AccountMapping::REMOVING ? |
| 294 kGCMRemoveMappingMessageTTL : kGCMAddMappingMessageTTL; |
| 295 return account_mapping.status_change_timestamp + |
| 296 base::TimeDelta::FromSeconds(ttl_seconds) < clock_->Now(); |
| 297 } |
| 298 |
| 299 AccountMapping* GCMAccountMapper::FindMappingByAccountId( |
| 300 const std::string& account_id) { |
| 301 for (AccountMappings::iterator iter = accounts_.begin(); |
| 302 iter != accounts_.end(); |
| 303 ++iter) { |
| 304 if (iter->account_id == account_id) |
| 305 return &*iter; |
| 306 } |
| 307 |
| 308 return NULL; |
| 309 } |
| 310 |
| 311 GCMAccountMapper::AccountMappings::iterator |
| 312 GCMAccountMapper::FindMappingByMessageId(const std::string& message_id) { |
| 313 for (std::vector<AccountMapping>::iterator iter = accounts_.begin(); |
| 314 iter != accounts_.end(); |
| 315 ++iter) { |
| 316 if (iter->last_message_id == message_id) |
| 317 return iter; |
| 318 } |
| 319 |
| 320 return accounts_.end(); |
| 321 } |
| 322 |
| 323 void GCMAccountMapper::SetClockForTesting(scoped_ptr<base::Clock> clock) { |
| 324 clock_ = clock.Pass(); |
| 325 } |
| 326 |
| 327 } // namespace gcm |
OLD | NEW |