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 |