Chromium Code Reviews| 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 is dependent on a fresh OAuth2 token, we | |
|
jianli
2014/09/02 18:00:10
is dependent => depends
fgorski
2014/09/03 21:37:02
Done.
| |
| 24 // allow the update to happen earlier then update interval, if it is within | |
|
jianli
2014/09/02 18:00:09
then update interval => than update due time
fgorski
2014/09/03 21:37:02
Done.
| |
| 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 kRemoveAccountMessageKey[] = "r"; | |
| 31 const char kRemoveAccountMessageValue[] = "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 removing, | |
|
jianli
2014/09/02 18:00:10
removing => being removed
fgorski
2014/09/03 21:37:02
Done.
| |
| 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 // TODO(fgorski): What other status can we get here if any? What should we do | |
| 157 // about that. (likely TTL_EXCEEDED is the only one) | |
| 158 DCHECK_EQ(send_error_details.result, GCMClient::TTL_EXCEEDED); | |
| 159 | |
| 160 if (account_mapping_it->status == AccountMapping::REMOVING) { | |
|
jianli
2014/09/02 18:00:10
It would be better to add the check for TTL_EXCEED
fgorski
2014/09/03 21:37:02
Done.
| |
| 161 // Another message to remove mapping can be sent immediately, because TTL | |
| 162 // for those is one day. No need to back off. | |
| 163 SendRemoveMappingMessage(*account_mapping_it); | |
| 164 } else { | |
| 165 if (account_mapping_it->status == AccountMapping::ADDING) { | |
| 166 // There is no mapping established, so we can remove the entry. | |
| 167 // Getting a fresh token will trigger a new attempt. | |
| 168 gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id); | |
| 169 accounts_.erase(account_mapping_it); | |
| 170 } else { | |
| 171 // Account is already MAPPED, we have to wait for another token. | |
| 172 account_mapping_it->last_message_id.clear(); | |
| 173 gcm_driver_->UpdateAccountMapping(*account_mapping_it); | |
| 174 } | |
| 175 } | |
| 176 } | |
| 177 | |
| 178 void GCMAccountMapper::OnSendAcknowledged(const std::string& app_id, | |
| 179 const std::string& message_id) { | |
| 180 DCHECK_EQ(app_id, kGCMAccountMapperAppId); | |
| 181 AccountMappings::iterator account_mapping_it = | |
| 182 FindMappingByMessageId(message_id); | |
| 183 | |
| 184 DVLOG(1) << "OnSendAcknowledged with message ID: " << message_id; | |
| 185 | |
| 186 if (account_mapping_it == accounts_.end()) | |
| 187 return; | |
| 188 | |
| 189 // Here is where we advance a status of a mapping and persist or remove. | |
| 190 if (account_mapping_it->status == AccountMapping::REMOVING) { | |
| 191 // Message removing the account has been confirmed by the GCM, we can remove | |
| 192 // all the information related to the account (from memory and store). | |
| 193 gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id); | |
| 194 accounts_.erase(account_mapping_it); | |
| 195 } else { | |
| 196 // Mapping status is ADDING only when it is a first time mapping. | |
| 197 DCHECK(account_mapping_it->status == AccountMapping::ADDING || | |
| 198 account_mapping_it->status == AccountMapping::MAPPED); | |
| 199 | |
| 200 // Account is marked as mapped with the current time. | |
| 201 account_mapping_it->status = AccountMapping::MAPPED; | |
| 202 account_mapping_it->status_change_timestamp = clock_->Now(); | |
| 203 // There is no pending message for the account. | |
| 204 account_mapping_it->last_message_id.clear(); | |
| 205 | |
| 206 gcm_driver_->UpdateAccountMapping(*account_mapping_it); | |
| 207 } | |
| 208 } | |
| 209 | |
| 210 bool GCMAccountMapper::CanHandle(const std::string& app_id) const { | |
| 211 return app_id.compare(kGCMAccountMapperAppId) == 0; | |
| 212 } | |
| 213 | |
| 214 void GCMAccountMapper::SendAddMappingMessage(AccountMapping& account_mapping) { | |
| 215 GCMClient::OutgoingMessage outgoing_message; | |
| 216 outgoing_message.id = GenerateMessageID(); | |
| 217 outgoing_message.time_to_live = kGCMAddMappingMessageTTL; | |
| 218 outgoing_message.data[kRegistrationIdMessgaeKey] = registration_id_; | |
| 219 outgoing_message.data[kTokenMessageKey] = account_mapping.access_token; | |
| 220 outgoing_message.data[kAccountMessageKey] = account_mapping.email; | |
| 221 | |
| 222 gcm_driver_->Send(kGCMAccountMapperAppId, | |
| 223 kGCMAccountMapperSenderId, | |
| 224 outgoing_message, | |
| 225 base::Bind(&GCMAccountMapper::OnSendFinished, | |
| 226 weak_ptr_factory_.GetWeakPtr(), | |
| 227 account_mapping.account_id)); | |
| 228 } | |
| 229 | |
| 230 void GCMAccountMapper::SendRemoveMappingMessage( | |
| 231 AccountMapping& account_mapping) { | |
| 232 // We want to persist an account that is being removed as quickly as possible | |
| 233 // as well as clean up the last message information. | |
| 234 if (account_mapping.status != AccountMapping::REMOVING) { | |
| 235 account_mapping.status = AccountMapping::REMOVING; | |
| 236 account_mapping.status_change_timestamp = clock_->Now(); | |
| 237 } | |
| 238 | |
| 239 account_mapping.last_message_id.clear(); | |
| 240 | |
| 241 gcm_driver_->UpdateAccountMapping(account_mapping); | |
| 242 | |
| 243 GCMClient::OutgoingMessage outgoing_message; | |
| 244 outgoing_message.id = GenerateMessageID(); | |
| 245 outgoing_message.time_to_live = kGCMRemoveMappingMessageTTL; | |
| 246 outgoing_message.data[kRegistrationIdMessgaeKey] = registration_id_; | |
| 247 outgoing_message.data[kAccountMessageKey] = account_mapping.email; | |
| 248 outgoing_message.data[kRemoveAccountMessageKey] = kRemoveAccountMessageValue; | |
|
jianli
2014/09/02 18:00:10
The code in line 241-254 is very similar to the co
fgorski
2014/09/03 21:37:02
Done.
| |
| 249 | |
| 250 gcm_driver_->Send(kGCMAccountMapperAppId, | |
| 251 kGCMAccountMapperSenderId, | |
| 252 outgoing_message, | |
| 253 base::Bind(&GCMAccountMapper::OnSendFinished, | |
| 254 weak_ptr_factory_.GetWeakPtr(), | |
| 255 account_mapping.account_id)); | |
| 256 } | |
| 257 | |
| 258 void GCMAccountMapper::OnSendFinished(const std::string& account_id, | |
| 259 const std::string& message_id, | |
| 260 GCMClient::Result result) { | |
| 261 // TODO(fgorski): Add another attempt, in case the QUEUE is not full. | |
| 262 if (result != GCMClient::SUCCESS) | |
| 263 return; | |
| 264 | |
| 265 AccountMapping* account_mapping = FindMappingByAccountId(account_id); | |
| 266 DCHECK(account_mapping); | |
| 267 | |
| 268 // If we are dealing with account with status NEW, it is the first time | |
| 269 // mapping, and we should mark it as ADDING. | |
| 270 if (account_mapping->status == AccountMapping::NEW) { | |
| 271 account_mapping->status = AccountMapping::ADDING; | |
| 272 account_mapping->status_change_timestamp = clock_->Now(); | |
| 273 } | |
| 274 | |
| 275 account_mapping->last_message_id = message_id; | |
| 276 | |
| 277 gcm_driver_->UpdateAccountMapping(*account_mapping); | |
| 278 } | |
| 279 | |
| 280 bool GCMAccountMapper::CanTriggerUpdate( | |
| 281 const base::Time& last_update_time) const { | |
| 282 return last_update_time + | |
| 283 base::TimeDelta::FromHours(kGCMUpdateIntervalHours - | |
| 284 kGCMUpdateEarlyStartHours) < | |
| 285 clock_->Now(); | |
| 286 } | |
| 287 | |
| 288 bool GCMAccountMapper::IsLastStatusChangeOlderThanTTL( | |
| 289 const AccountMapping& account_mapping) const { | |
| 290 base::TimeDelta time_to_live; | |
|
jianli
2014/09/02 18:00:10
Probably more efficient to say:
int ttl_seconds
fgorski
2014/09/03 21:37:02
Done.
| |
| 291 if (account_mapping.status == AccountMapping::REMOVING) | |
| 292 time_to_live = base::TimeDelta::FromSeconds(kGCMRemoveMappingMessageTTL); | |
| 293 else | |
| 294 time_to_live = base::TimeDelta::FromSeconds(kGCMAddMappingMessageTTL); | |
| 295 return account_mapping.status_change_timestamp + time_to_live < clock_->Now(); | |
| 296 } | |
| 297 | |
| 298 AccountMapping* GCMAccountMapper::FindMappingByAccountId( | |
| 299 const std::string& account_id) { | |
| 300 for (AccountMappings::iterator iter = accounts_.begin(); | |
| 301 iter != accounts_.end(); | |
| 302 ++iter) { | |
| 303 if (iter->account_id == account_id) | |
| 304 return &*iter; | |
| 305 } | |
| 306 | |
| 307 return NULL; | |
| 308 } | |
| 309 | |
| 310 GCMAccountMapper::AccountMappings::iterator | |
| 311 GCMAccountMapper::FindMappingByMessageId(const std::string& message_id) { | |
| 312 for (std::vector<AccountMapping>::iterator iter = accounts_.begin(); | |
| 313 iter != accounts_.end(); | |
| 314 ++iter) { | |
| 315 if (iter->last_message_id == message_id) | |
| 316 return iter; | |
| 317 } | |
| 318 | |
| 319 return accounts_.end(); | |
| 320 } | |
| 321 | |
| 322 void GCMAccountMapper::SetClockForTesting(scoped_ptr<base::Clock> clock) { | |
| 323 clock_ = clock.Pass(); | |
| 324 } | |
| 325 | |
| 326 } // namespace gcm | |
| OLD | NEW |