Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(26)

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClient.java

Issue 2664253005: [Omaha] Move most functionality to OmahaBase, add JobService (Closed)
Patch Set: [Omaha] Move most functionality to OmahaBase, add JobService Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.chrome.browser.omaha; 5 package org.chromium.chrome.browser.omaha;
6 6
7 import android.app.IntentService; 7 import android.app.IntentService;
8 import android.content.Context; 8 import android.content.Context;
9 import android.content.Intent; 9 import android.content.Intent;
10 import android.content.SharedPreferences;
11 import android.os.Build;
12 import android.support.annotation.IntDef;
13
14 import org.chromium.base.Log;
15 import org.chromium.base.ThreadUtils;
16 import org.chromium.base.VisibleForTesting;
17
18 import java.io.IOException;
19 import java.lang.annotation.Retention;
20 import java.lang.annotation.RetentionPolicy;
21 import java.net.HttpURLConnection;
22 import java.net.MalformedURLException;
23 import java.net.URL;
24 import java.util.concurrent.TimeUnit;
25 10
26 /** 11 /**
27 * Keeps tabs on the current state of Chrome, tracking if and when a request sho uld be sent to the 12 * Runs the {@link OmahaBase} pipeline as a {@link IntentService}.
28 * Omaha Server.
29 *
30 * A hook in ChromeActivity's doDeferredResume() initializes the service. Furth er attempts to
31 * reschedule events will be scheduled by the class itself.
32 *
33 * Each request to the server will perform an update check and ping the server.
34 * We use a repeating alarm to schedule the XML requests to be generated 5 hours apart.
35 * If Chrome isn't running when the alarm is fired, the request generation will be stalled until
36 * the next time Chrome runs.
37 *
38 * mevissen suggested being conservative with our timers for sending requests.
39 * POST attempts that fail to be acknowledged by the server are re-attempted, wi th at least
40 * one hour between each attempt.
41 *
42 * Status is saved directly to the the disk after every operation. Unit tests t esting the code
43 * paths without using Intents may need to call restoreState() manually as it is not automatically
44 * handled in onCreate().
45 *
46 * Implementation notes:
47 * http://docs.google.com/a/google.com/document/d/1scTCovqASf5ktkOeVj8wFRkWTCeDY w2LrOBNn05CDB0/edit
48 * 13 *
49 * NOTE: This class can never be renamed because the user may have Intents float ing around that 14 * NOTE: This class can never be renamed because the user may have Intents float ing around that
50 * reference this class specifically. 15 * reference this class specifically.
51 */ 16 */
52 public class OmahaClient extends IntentService { 17 public class OmahaClient extends IntentService {
53 // Results of {@link #handlePostRequest()}.
54 @Retention(RetentionPolicy.SOURCE)
55 @IntDef({POST_RESULT_NO_REQUEST, POST_RESULT_SENT, POST_RESULT_FAILED, POST_ RESULT_SCHEDULED})
56 @interface PostResult {}
57 static final int POST_RESULT_NO_REQUEST = 0;
58 static final int POST_RESULT_SENT = 1;
59 static final int POST_RESULT_FAILED = 2;
60 static final int POST_RESULT_SCHEDULED = 3;
61
62 private static final String TAG = "omaha"; 18 private static final String TAG = "omaha";
63 19
64 /** Deprecated; kept around to cancel alarms set for OmahaClient pre-M58. */
65 private static final String ACTION_REGISTER_REQUEST =
66 "org.chromium.chrome.browser.omaha.ACTION_REGISTER_REQUEST";
67
68 // Delays between events.
69 static final long MS_POST_BASE_DELAY = TimeUnit.HOURS.toMillis(1);
70 static final long MS_POST_MAX_DELAY = TimeUnit.HOURS.toMillis(5);
71 static final long MS_BETWEEN_REQUESTS = TimeUnit.HOURS.toMillis(5);
72 static final int MS_CONNECTION_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(1);
73
74 // Strings indicating how the Chrome APK arrived on the user's device. These values MUST NOT
75 // be changed without updating the corresponding Omaha server strings.
76 static final String INSTALL_SOURCE_SYSTEM = "system_image";
77 static final String INSTALL_SOURCE_ORGANIC = "organic";
78
79 private static final long INVALID_TIMESTAMP = -1;
80 @VisibleForTesting
81 static final String INVALID_REQUEST_ID = "invalid";
82
83 // Member fields not persisted to disk.
84 private boolean mStateHasBeenRestored;
85 private OmahaDelegate mDelegate;
86
87 // State saved written to and read from disk.
88 private RequestData mCurrentRequest;
89 private long mTimestampOfInstall;
90 private long mTimestampForNextPostAttempt;
91 private long mTimestampForNewRequest;
92 private String mLatestVersion;
93 private String mMarketURL;
94 private String mInstallSource;
95 protected boolean mSendInstallEvent;
96
97 public OmahaClient() { 20 public OmahaClient() {
98 super(TAG); 21 super(TAG);
99 setIntentRedelivery(true); 22 setIntentRedelivery(true);
100 } 23 }
101 24
102 /**
103 * Handles an action on a thread separate from the UI thread.
104 * @param intent Intent fired by some part of Chrome.
105 */
106 @Override 25 @Override
107 public void onHandleIntent(Intent intent) { 26 public void onHandleIntent(Intent intent) {
108 assert !ThreadUtils.runningOnUiThread(); 27 OmahaService.getInstance(this).run();
109 run();
110 } 28 }
111 29
112 protected void run() { 30 static Intent createIntent(Context context) {
113 if (mDelegate == null) mDelegate = new OmahaDelegateImpl(this);
114
115 if (OmahaBase.isDisabled() || Build.VERSION.SDK_INT > Build.VERSION_CODE S.N
116 || getRequestGenerator() == null) {
117 Log.v(TAG, "Disabled. Ignoring intent.");
118 return;
119 }
120
121 restoreState(getContext());
122
123 long nextTimestamp = Long.MAX_VALUE;
124 if (mDelegate.isChromeBeingUsed()) {
125 handleRegisterActiveRequest();
126 nextTimestamp = Math.min(nextTimestamp, mTimestampForNewRequest);
127 }
128
129 if (hasRequest()) {
130 int result = handlePostRequest();
131 if (result == POST_RESULT_FAILED || result == POST_RESULT_SCHEDULED) {
132 nextTimestamp = Math.min(nextTimestamp, mTimestampForNextPostAtt empt);
133 }
134 }
135
136 // TODO(dfalcantara): Prevent Omaha code from repeatedly rescheduling it self immediately in
137 // case a scheduling error occurs.
138 if (nextTimestamp != Long.MAX_VALUE && nextTimestamp >= 0) {
139 mDelegate.scheduleService(this, nextTimestamp);
140 }
141 saveState(getContext());
142 }
143
144 /**
145 * Begin communicating with the Omaha Update Server.
146 */
147 static void startService(Context context) {
148 context.startService(createOmahaIntent(context));
149 }
150
151 static Intent createOmahaIntent(Context context) {
152 return new Intent(context, OmahaClient.class); 31 return new Intent(context, OmahaClient.class);
153 } 32 }
154
155 /**
156 * Determines if a new request should be generated. New requests are only g enerated if enough
157 * time has passed between now and the last time a request was generated.
158 */
159 private void handleRegisterActiveRequest() {
160 // If the current request is too old, generate a new one.
161 long currentTimestamp = getBackoffScheduler().getCurrentTime();
162 boolean isTooOld = hasRequest()
163 && mCurrentRequest.getAgeInMilliseconds(currentTimestamp) >= MS_ BETWEEN_REQUESTS;
164 boolean isOverdue = currentTimestamp >= mTimestampForNewRequest;
165 if (isTooOld || isOverdue) {
166 registerNewRequest(currentTimestamp);
167 }
168 }
169
170 /**
171 * Sends the request it is holding.
172 */
173 private int handlePostRequest() {
174 if (!hasRequest()) {
175 mDelegate.onHandlePostRequestDone(POST_RESULT_NO_REQUEST, false);
176 return POST_RESULT_NO_REQUEST;
177 }
178
179 // If enough time has passed since the last attempt, try sending a reque st.
180 int result;
181 long currentTimestamp = getBackoffScheduler().getCurrentTime();
182 boolean installEventWasSent = false;
183 if (currentTimestamp >= mTimestampForNextPostAttempt) {
184 // All requests made during the same session should have the same ID .
185 String sessionID = mDelegate.generateUUID();
186 boolean sendingInstallRequest = mSendInstallEvent;
187 boolean succeeded = generateAndPostRequest(currentTimestamp, session ID);
188
189 if (succeeded && sendingInstallRequest) {
190 // Only the first request ever generated should contain an insta ll event.
191 mSendInstallEvent = false;
192 installEventWasSent = true;
193
194 // Create and immediately send another request for a ping and up date check.
195 registerNewRequest(currentTimestamp);
196 succeeded &= generateAndPostRequest(currentTimestamp, sessionID) ;
197 }
198
199 result = succeeded ? POST_RESULT_SENT : POST_RESULT_FAILED;
200 } else {
201 result = POST_RESULT_SCHEDULED;
202 }
203
204 mDelegate.onHandlePostRequestDone(result, installEventWasSent);
205 return result;
206 }
207
208 private boolean generateAndPostRequest(long currentTimestamp, String session ID) {
209 ExponentialBackoffScheduler scheduler = getBackoffScheduler();
210 boolean succeeded = false;
211 try {
212 // Generate the XML for the current request.
213 long installAgeInDays = RequestGenerator.installAge(currentTimestamp ,
214 mTimestampOfInstall, mCurrentRequest.isSendInstallEvent());
215 String version = VersionNumberGetter.getInstance().getCurrentlyUsedV ersion(this);
216 String xml = getRequestGenerator().generateXML(
217 sessionID, version, installAgeInDays, mCurrentRequest);
218
219 // Send the request to the server & wait for a response.
220 String response = postRequest(currentTimestamp, xml);
221
222 // Parse out the response.
223 String appId = getRequestGenerator().getAppId();
224 boolean sentPingAndUpdate = !mSendInstallEvent;
225 ResponseParser parser = new ResponseParser(
226 appId, mSendInstallEvent, sentPingAndUpdate, sentPingAndUpda te);
227 parser.parseResponse(response);
228 mLatestVersion = parser.getNewVersion();
229 mMarketURL = parser.getURL();
230
231 succeeded = true;
232 } catch (RequestFailureException e) {
233 Log.e(TAG, "Failed to contact server: ", e);
234 }
235
236 if (succeeded) {
237 // If we've gotten this far, we've successfully sent a request.
238 mCurrentRequest = null;
239
240 scheduler.resetFailedAttempts();
241 mTimestampForNewRequest = scheduler.getCurrentTime() + MS_BETWEEN_RE QUESTS;
242 mTimestampForNextPostAttempt = scheduler.calculateNextTimestamp();
243 Log.i(TAG, "Request to Server Successful. Timestamp for next request :"
244 + String.valueOf(mTimestampForNextPostAttempt));
245 } else {
246 // Set the alarm to try again later. Failures are incremented after setting the timer
247 // to allow the first failure to incur the minimum base delay betwee n POSTs.
248 mTimestampForNextPostAttempt = scheduler.calculateNextTimestamp();
249 scheduler.increaseFailedAttempts();
250 }
251
252 mDelegate.onGenerateAndPostRequestDone(succeeded);
253 return succeeded;
254 }
255
256 /**
257 * Registers a new request with the current timestamp. Internal timestamps are reset to start
258 * fresh.
259 * @param currentTimestamp Current time.
260 */
261 private void registerNewRequest(long currentTimestamp) {
262 mCurrentRequest = createRequestData(currentTimestamp, null);
263 getBackoffScheduler().resetFailedAttempts();
264 mTimestampForNextPostAttempt = currentTimestamp;
265
266 // Tentatively set the timestamp for a new request. This will be update d when the server
267 // is successfully contacted.
268 mTimestampForNewRequest = currentTimestamp + MS_BETWEEN_REQUESTS;
269
270 mDelegate.onRegisterNewRequestDone(mTimestampForNewRequest, mTimestampFo rNextPostAttempt);
271 }
272
273 private RequestData createRequestData(long currentTimestamp, String persiste dID) {
274 // If we're sending a persisted event, keep trying to send the same requ est ID.
275 String requestID;
276 if (persistedID == null || INVALID_REQUEST_ID.equals(persistedID)) {
277 requestID = mDelegate.generateUUID();
278 } else {
279 requestID = persistedID;
280 }
281 return new RequestData(mSendInstallEvent, currentTimestamp, requestID, m InstallSource);
282 }
283
284 private boolean hasRequest() {
285 return mCurrentRequest != null;
286 }
287
288 /**
289 * Posts the request to the Omaha server.
290 * @return the XML response as a String.
291 * @throws RequestFailureException if the request fails.
292 */
293 private String postRequest(long timestamp, String xml) throws RequestFailure Exception {
294 String response = null;
295
296 HttpURLConnection urlConnection = null;
297 try {
298 urlConnection = createConnection();
299
300 // Prepare the HTTP header.
301 urlConnection.setDoOutput(true);
302 urlConnection.setFixedLengthStreamingMode(xml.getBytes().length);
303 if (mSendInstallEvent && getBackoffScheduler().getNumFailedAttempts( ) > 0) {
304 String age = Long.toString(mCurrentRequest.getAgeInSeconds(times tamp));
305 urlConnection.addRequestProperty("X-RequestAge", age);
306 }
307
308 response = OmahaBase.sendRequestToServer(urlConnection, xml);
309 } catch (IllegalAccessError e) {
310 throw new RequestFailureException("Caught an IllegalAccessError:", e );
311 } catch (IllegalArgumentException e) {
312 throw new RequestFailureException("Caught an IllegalArgumentExceptio n:", e);
313 } catch (IllegalStateException e) {
314 throw new RequestFailureException("Caught an IllegalStateException:" , e);
315 } finally {
316 if (urlConnection != null) {
317 urlConnection.disconnect();
318 }
319 }
320
321 return response;
322 }
323
324 /**
325 * Returns a HttpURLConnection to the server.
326 */
327 @VisibleForTesting
328 protected HttpURLConnection createConnection() throws RequestFailureExceptio n {
329 try {
330 URL url = new URL(getRequestGenerator().getServerUrl());
331 HttpURLConnection connection = (HttpURLConnection) url.openConnectio n();
332 connection.setConnectTimeout(MS_CONNECTION_TIMEOUT);
333 connection.setReadTimeout(MS_CONNECTION_TIMEOUT);
334 return connection;
335 } catch (MalformedURLException e) {
336 throw new RequestFailureException("Caught a malformed URL exception. ", e);
337 } catch (IOException e) {
338 throw new RequestFailureException("Failed to open connection to URL" , e);
339 }
340 }
341
342 /**
343 * Reads the data back from the file it was saved to. Uses SharedPreference s to handle I/O.
344 * Sanity checks are performed on the timestamps to guard against clock chan ging.
345 */
346 @VisibleForTesting
347 void restoreState(Context context) {
348 if (mStateHasBeenRestored) return;
349
350 String installSource =
351 mDelegate.isInSystemImage() ? INSTALL_SOURCE_SYSTEM : INSTALL_SO URCE_ORGANIC;
352 ExponentialBackoffScheduler scheduler = getBackoffScheduler();
353 long currentTime = scheduler.getCurrentTime();
354
355 SharedPreferences preferences = OmahaBase.getSharedPreferences(context);
356 mTimestampForNewRequest =
357 preferences.getLong(OmahaBase.PREF_TIMESTAMP_FOR_NEW_REQUEST, cu rrentTime);
358 mTimestampForNextPostAttempt =
359 preferences.getLong(OmahaBase.PREF_TIMESTAMP_FOR_NEXT_POST_ATTEM PT, currentTime);
360 mTimestampOfInstall = preferences.getLong(OmahaBase.PREF_TIMESTAMP_OF_IN STALL, currentTime);
361 mSendInstallEvent = preferences.getBoolean(OmahaBase.PREF_SEND_INSTALL_E VENT, true);
362 mInstallSource = preferences.getString(OmahaBase.PREF_INSTALL_SOURCE, in stallSource);
363 mLatestVersion = preferences.getString(OmahaBase.PREF_LATEST_VERSION, "" );
364 mMarketURL = preferences.getString(OmahaBase.PREF_MARKET_URL, "");
365
366 // If we're not sending an install event, don't bother restoring the req uest ID:
367 // the server does not expect to have persisted request IDs for pings or update checks.
368 String persistedRequestId = mSendInstallEvent
369 ? preferences.getString(OmahaBase.PREF_PERSISTED_REQUEST_ID, INV ALID_REQUEST_ID)
370 : INVALID_REQUEST_ID;
371 long requestTimestamp =
372 preferences.getLong(OmahaBase.PREF_TIMESTAMP_OF_REQUEST, INVALID _TIMESTAMP);
373 mCurrentRequest = requestTimestamp == INVALID_TIMESTAMP
374 ? null : createRequestData(requestTimestamp, persistedRequestId) ;
375
376 // Confirm that the timestamp for the next request is less than the base delay.
377 long delayToNewRequest = mTimestampForNewRequest - currentTime;
378 if (delayToNewRequest > MS_BETWEEN_REQUESTS) {
379 Log.w(TAG, "Delay to next request (" + delayToNewRequest
380 + ") is longer than expected. Resetting to now.");
381 mTimestampForNewRequest = currentTime;
382 }
383
384 // Confirm that the timestamp for the next POST is less than the current delay.
385 long delayToNextPost = mTimestampForNextPostAttempt - currentTime;
386 long lastGeneratedDelay = scheduler.getGeneratedDelay();
387 if (delayToNextPost > lastGeneratedDelay) {
388 Log.w(TAG, "Delay to next post attempt (" + delayToNextPost
389 + ") is greater than expected (" + lastGeneratedDela y
390 + "). Resetting to now.");
391 mTimestampForNextPostAttempt = currentTime;
392 }
393
394 migrateToNewerChromeVersions();
395 mStateHasBeenRestored = true;
396 }
397
398 /**
399 * Writes out the current state to a file.
400 */
401 private void saveState(Context context) {
402 SharedPreferences prefs = OmahaBase.getSharedPreferences(context);
403 SharedPreferences.Editor editor = prefs.edit();
404 editor.putBoolean(OmahaBase.PREF_SEND_INSTALL_EVENT, mSendInstallEvent);
405 editor.putLong(OmahaBase.PREF_TIMESTAMP_OF_INSTALL, mTimestampOfInstall) ;
406 editor.putLong(
407 OmahaBase.PREF_TIMESTAMP_FOR_NEXT_POST_ATTEMPT, mTimestampForNex tPostAttempt);
408 editor.putLong(OmahaBase.PREF_TIMESTAMP_FOR_NEW_REQUEST, mTimestampForNe wRequest);
409 editor.putLong(OmahaBase.PREF_TIMESTAMP_OF_REQUEST,
410 hasRequest() ? mCurrentRequest.getCreationTimestamp() : INVALID_ TIMESTAMP);
411 editor.putString(OmahaBase.PREF_PERSISTED_REQUEST_ID,
412 hasRequest() ? mCurrentRequest.getRequestID() : INVALID_REQUEST_ ID);
413 editor.putString(
414 OmahaBase.PREF_LATEST_VERSION, mLatestVersion == null ? "" : mLa testVersion);
415 editor.putString(OmahaBase.PREF_MARKET_URL, mMarketURL == null ? "" : mM arketURL);
416 editor.putString(OmahaBase.PREF_INSTALL_SOURCE, mInstallSource);
417 editor.apply();
418
419 mDelegate.onSaveStateDone(mTimestampForNewRequest, mTimestampForNextPost Attempt);
420 }
421
422 private void migrateToNewerChromeVersions() {
423 // Remove any repeating alarms in favor of the new scheduling setup on M 58 and up.
424 // Seems cheaper to cancel the alarm repeatedly than to store a SharedPr eference and never
425 // do it again.
426 Intent intent = new Intent(getContext(), OmahaClient.class);
427 intent.setAction(ACTION_REGISTER_REQUEST);
428 getBackoffScheduler().cancelAlarm(intent);
429 }
430
431 Context getContext() {
432 return mDelegate.getContext();
433 }
434
435 private RequestGenerator getRequestGenerator() {
436 return mDelegate.getRequestGenerator();
437 }
438
439 private ExponentialBackoffScheduler getBackoffScheduler() {
440 return mDelegate.getScheduler();
441 }
442
443 void setDelegateForTests(OmahaDelegate delegate) {
444 mDelegate = delegate;
445 }
446 } 33 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698