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

Unified 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, 10 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 side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClient.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClient.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClient.java
index cf13a1f027f3359b61bc97122dd58ab1ac8744e1..bd895741fca90b24c847c73c90cfa2bb18376217 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClient.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClient.java
@@ -7,440 +7,27 @@ package org.chromium.chrome.browser.omaha;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Build;
-import android.support.annotation.IntDef;
-
-import org.chromium.base.Log;
-import org.chromium.base.ThreadUtils;
-import org.chromium.base.VisibleForTesting;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.concurrent.TimeUnit;
/**
- * Keeps tabs on the current state of Chrome, tracking if and when a request should be sent to the
- * Omaha Server.
- *
- * A hook in ChromeActivity's doDeferredResume() initializes the service. Further attempts to
- * reschedule events will be scheduled by the class itself.
- *
- * Each request to the server will perform an update check and ping the server.
- * We use a repeating alarm to schedule the XML requests to be generated 5 hours apart.
- * If Chrome isn't running when the alarm is fired, the request generation will be stalled until
- * the next time Chrome runs.
- *
- * mevissen suggested being conservative with our timers for sending requests.
- * POST attempts that fail to be acknowledged by the server are re-attempted, with at least
- * one hour between each attempt.
- *
- * Status is saved directly to the the disk after every operation. Unit tests testing the code
- * paths without using Intents may need to call restoreState() manually as it is not automatically
- * handled in onCreate().
- *
- * Implementation notes:
- * http://docs.google.com/a/google.com/document/d/1scTCovqASf5ktkOeVj8wFRkWTCeDYw2LrOBNn05CDB0/edit
+ * Runs the {@link OmahaBase} pipeline as a {@link IntentService}.
*
* NOTE: This class can never be renamed because the user may have Intents floating around that
* reference this class specifically.
*/
public class OmahaClient extends IntentService {
- // Results of {@link #handlePostRequest()}.
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({POST_RESULT_NO_REQUEST, POST_RESULT_SENT, POST_RESULT_FAILED, POST_RESULT_SCHEDULED})
- @interface PostResult {}
- static final int POST_RESULT_NO_REQUEST = 0;
- static final int POST_RESULT_SENT = 1;
- static final int POST_RESULT_FAILED = 2;
- static final int POST_RESULT_SCHEDULED = 3;
-
private static final String TAG = "omaha";
- /** Deprecated; kept around to cancel alarms set for OmahaClient pre-M58. */
- private static final String ACTION_REGISTER_REQUEST =
- "org.chromium.chrome.browser.omaha.ACTION_REGISTER_REQUEST";
-
- // Delays between events.
- static final long MS_POST_BASE_DELAY = TimeUnit.HOURS.toMillis(1);
- static final long MS_POST_MAX_DELAY = TimeUnit.HOURS.toMillis(5);
- static final long MS_BETWEEN_REQUESTS = TimeUnit.HOURS.toMillis(5);
- static final int MS_CONNECTION_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(1);
-
- // Strings indicating how the Chrome APK arrived on the user's device. These values MUST NOT
- // be changed without updating the corresponding Omaha server strings.
- static final String INSTALL_SOURCE_SYSTEM = "system_image";
- static final String INSTALL_SOURCE_ORGANIC = "organic";
-
- private static final long INVALID_TIMESTAMP = -1;
- @VisibleForTesting
- static final String INVALID_REQUEST_ID = "invalid";
-
- // Member fields not persisted to disk.
- private boolean mStateHasBeenRestored;
- private OmahaDelegate mDelegate;
-
- // State saved written to and read from disk.
- private RequestData mCurrentRequest;
- private long mTimestampOfInstall;
- private long mTimestampForNextPostAttempt;
- private long mTimestampForNewRequest;
- private String mLatestVersion;
- private String mMarketURL;
- private String mInstallSource;
- protected boolean mSendInstallEvent;
-
public OmahaClient() {
super(TAG);
setIntentRedelivery(true);
}
- /**
- * Handles an action on a thread separate from the UI thread.
- * @param intent Intent fired by some part of Chrome.
- */
@Override
public void onHandleIntent(Intent intent) {
- assert !ThreadUtils.runningOnUiThread();
- run();
+ OmahaService.getInstance(this).run();
}
- protected void run() {
- if (mDelegate == null) mDelegate = new OmahaDelegateImpl(this);
-
- if (OmahaBase.isDisabled() || Build.VERSION.SDK_INT > Build.VERSION_CODES.N
- || getRequestGenerator() == null) {
- Log.v(TAG, "Disabled. Ignoring intent.");
- return;
- }
-
- restoreState(getContext());
-
- long nextTimestamp = Long.MAX_VALUE;
- if (mDelegate.isChromeBeingUsed()) {
- handleRegisterActiveRequest();
- nextTimestamp = Math.min(nextTimestamp, mTimestampForNewRequest);
- }
-
- if (hasRequest()) {
- int result = handlePostRequest();
- if (result == POST_RESULT_FAILED || result == POST_RESULT_SCHEDULED) {
- nextTimestamp = Math.min(nextTimestamp, mTimestampForNextPostAttempt);
- }
- }
-
- // TODO(dfalcantara): Prevent Omaha code from repeatedly rescheduling itself immediately in
- // case a scheduling error occurs.
- if (nextTimestamp != Long.MAX_VALUE && nextTimestamp >= 0) {
- mDelegate.scheduleService(this, nextTimestamp);
- }
- saveState(getContext());
- }
-
- /**
- * Begin communicating with the Omaha Update Server.
- */
- static void startService(Context context) {
- context.startService(createOmahaIntent(context));
- }
-
- static Intent createOmahaIntent(Context context) {
+ static Intent createIntent(Context context) {
return new Intent(context, OmahaClient.class);
}
-
- /**
- * Determines if a new request should be generated. New requests are only generated if enough
- * time has passed between now and the last time a request was generated.
- */
- private void handleRegisterActiveRequest() {
- // If the current request is too old, generate a new one.
- long currentTimestamp = getBackoffScheduler().getCurrentTime();
- boolean isTooOld = hasRequest()
- && mCurrentRequest.getAgeInMilliseconds(currentTimestamp) >= MS_BETWEEN_REQUESTS;
- boolean isOverdue = currentTimestamp >= mTimestampForNewRequest;
- if (isTooOld || isOverdue) {
- registerNewRequest(currentTimestamp);
- }
- }
-
- /**
- * Sends the request it is holding.
- */
- private int handlePostRequest() {
- if (!hasRequest()) {
- mDelegate.onHandlePostRequestDone(POST_RESULT_NO_REQUEST, false);
- return POST_RESULT_NO_REQUEST;
- }
-
- // If enough time has passed since the last attempt, try sending a request.
- int result;
- long currentTimestamp = getBackoffScheduler().getCurrentTime();
- boolean installEventWasSent = false;
- if (currentTimestamp >= mTimestampForNextPostAttempt) {
- // All requests made during the same session should have the same ID.
- String sessionID = mDelegate.generateUUID();
- boolean sendingInstallRequest = mSendInstallEvent;
- boolean succeeded = generateAndPostRequest(currentTimestamp, sessionID);
-
- if (succeeded && sendingInstallRequest) {
- // Only the first request ever generated should contain an install event.
- mSendInstallEvent = false;
- installEventWasSent = true;
-
- // Create and immediately send another request for a ping and update check.
- registerNewRequest(currentTimestamp);
- succeeded &= generateAndPostRequest(currentTimestamp, sessionID);
- }
-
- result = succeeded ? POST_RESULT_SENT : POST_RESULT_FAILED;
- } else {
- result = POST_RESULT_SCHEDULED;
- }
-
- mDelegate.onHandlePostRequestDone(result, installEventWasSent);
- return result;
- }
-
- private boolean generateAndPostRequest(long currentTimestamp, String sessionID) {
- ExponentialBackoffScheduler scheduler = getBackoffScheduler();
- boolean succeeded = false;
- try {
- // Generate the XML for the current request.
- long installAgeInDays = RequestGenerator.installAge(currentTimestamp,
- mTimestampOfInstall, mCurrentRequest.isSendInstallEvent());
- String version = VersionNumberGetter.getInstance().getCurrentlyUsedVersion(this);
- String xml = getRequestGenerator().generateXML(
- sessionID, version, installAgeInDays, mCurrentRequest);
-
- // Send the request to the server & wait for a response.
- String response = postRequest(currentTimestamp, xml);
-
- // Parse out the response.
- String appId = getRequestGenerator().getAppId();
- boolean sentPingAndUpdate = !mSendInstallEvent;
- ResponseParser parser = new ResponseParser(
- appId, mSendInstallEvent, sentPingAndUpdate, sentPingAndUpdate);
- parser.parseResponse(response);
- mLatestVersion = parser.getNewVersion();
- mMarketURL = parser.getURL();
-
- succeeded = true;
- } catch (RequestFailureException e) {
- Log.e(TAG, "Failed to contact server: ", e);
- }
-
- if (succeeded) {
- // If we've gotten this far, we've successfully sent a request.
- mCurrentRequest = null;
-
- scheduler.resetFailedAttempts();
- mTimestampForNewRequest = scheduler.getCurrentTime() + MS_BETWEEN_REQUESTS;
- mTimestampForNextPostAttempt = scheduler.calculateNextTimestamp();
- Log.i(TAG, "Request to Server Successful. Timestamp for next request:"
- + String.valueOf(mTimestampForNextPostAttempt));
- } else {
- // Set the alarm to try again later. Failures are incremented after setting the timer
- // to allow the first failure to incur the minimum base delay between POSTs.
- mTimestampForNextPostAttempt = scheduler.calculateNextTimestamp();
- scheduler.increaseFailedAttempts();
- }
-
- mDelegate.onGenerateAndPostRequestDone(succeeded);
- return succeeded;
- }
-
- /**
- * Registers a new request with the current timestamp. Internal timestamps are reset to start
- * fresh.
- * @param currentTimestamp Current time.
- */
- private void registerNewRequest(long currentTimestamp) {
- mCurrentRequest = createRequestData(currentTimestamp, null);
- getBackoffScheduler().resetFailedAttempts();
- mTimestampForNextPostAttempt = currentTimestamp;
-
- // Tentatively set the timestamp for a new request. This will be updated when the server
- // is successfully contacted.
- mTimestampForNewRequest = currentTimestamp + MS_BETWEEN_REQUESTS;
-
- mDelegate.onRegisterNewRequestDone(mTimestampForNewRequest, mTimestampForNextPostAttempt);
- }
-
- private RequestData createRequestData(long currentTimestamp, String persistedID) {
- // If we're sending a persisted event, keep trying to send the same request ID.
- String requestID;
- if (persistedID == null || INVALID_REQUEST_ID.equals(persistedID)) {
- requestID = mDelegate.generateUUID();
- } else {
- requestID = persistedID;
- }
- return new RequestData(mSendInstallEvent, currentTimestamp, requestID, mInstallSource);
- }
-
- private boolean hasRequest() {
- return mCurrentRequest != null;
- }
-
- /**
- * Posts the request to the Omaha server.
- * @return the XML response as a String.
- * @throws RequestFailureException if the request fails.
- */
- private String postRequest(long timestamp, String xml) throws RequestFailureException {
- String response = null;
-
- HttpURLConnection urlConnection = null;
- try {
- urlConnection = createConnection();
-
- // Prepare the HTTP header.
- urlConnection.setDoOutput(true);
- urlConnection.setFixedLengthStreamingMode(xml.getBytes().length);
- if (mSendInstallEvent && getBackoffScheduler().getNumFailedAttempts() > 0) {
- String age = Long.toString(mCurrentRequest.getAgeInSeconds(timestamp));
- urlConnection.addRequestProperty("X-RequestAge", age);
- }
-
- response = OmahaBase.sendRequestToServer(urlConnection, xml);
- } catch (IllegalAccessError e) {
- throw new RequestFailureException("Caught an IllegalAccessError:", e);
- } catch (IllegalArgumentException e) {
- throw new RequestFailureException("Caught an IllegalArgumentException:", e);
- } catch (IllegalStateException e) {
- throw new RequestFailureException("Caught an IllegalStateException:", e);
- } finally {
- if (urlConnection != null) {
- urlConnection.disconnect();
- }
- }
-
- return response;
- }
-
- /**
- * Returns a HttpURLConnection to the server.
- */
- @VisibleForTesting
- protected HttpURLConnection createConnection() throws RequestFailureException {
- try {
- URL url = new URL(getRequestGenerator().getServerUrl());
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
- connection.setConnectTimeout(MS_CONNECTION_TIMEOUT);
- connection.setReadTimeout(MS_CONNECTION_TIMEOUT);
- return connection;
- } catch (MalformedURLException e) {
- throw new RequestFailureException("Caught a malformed URL exception.", e);
- } catch (IOException e) {
- throw new RequestFailureException("Failed to open connection to URL", e);
- }
- }
-
- /**
- * Reads the data back from the file it was saved to. Uses SharedPreferences to handle I/O.
- * Sanity checks are performed on the timestamps to guard against clock changing.
- */
- @VisibleForTesting
- void restoreState(Context context) {
- if (mStateHasBeenRestored) return;
-
- String installSource =
- mDelegate.isInSystemImage() ? INSTALL_SOURCE_SYSTEM : INSTALL_SOURCE_ORGANIC;
- ExponentialBackoffScheduler scheduler = getBackoffScheduler();
- long currentTime = scheduler.getCurrentTime();
-
- SharedPreferences preferences = OmahaBase.getSharedPreferences(context);
- mTimestampForNewRequest =
- preferences.getLong(OmahaBase.PREF_TIMESTAMP_FOR_NEW_REQUEST, currentTime);
- mTimestampForNextPostAttempt =
- preferences.getLong(OmahaBase.PREF_TIMESTAMP_FOR_NEXT_POST_ATTEMPT, currentTime);
- mTimestampOfInstall = preferences.getLong(OmahaBase.PREF_TIMESTAMP_OF_INSTALL, currentTime);
- mSendInstallEvent = preferences.getBoolean(OmahaBase.PREF_SEND_INSTALL_EVENT, true);
- mInstallSource = preferences.getString(OmahaBase.PREF_INSTALL_SOURCE, installSource);
- mLatestVersion = preferences.getString(OmahaBase.PREF_LATEST_VERSION, "");
- mMarketURL = preferences.getString(OmahaBase.PREF_MARKET_URL, "");
-
- // If we're not sending an install event, don't bother restoring the request ID:
- // the server does not expect to have persisted request IDs for pings or update checks.
- String persistedRequestId = mSendInstallEvent
- ? preferences.getString(OmahaBase.PREF_PERSISTED_REQUEST_ID, INVALID_REQUEST_ID)
- : INVALID_REQUEST_ID;
- long requestTimestamp =
- preferences.getLong(OmahaBase.PREF_TIMESTAMP_OF_REQUEST, INVALID_TIMESTAMP);
- mCurrentRequest = requestTimestamp == INVALID_TIMESTAMP
- ? null : createRequestData(requestTimestamp, persistedRequestId);
-
- // Confirm that the timestamp for the next request is less than the base delay.
- long delayToNewRequest = mTimestampForNewRequest - currentTime;
- if (delayToNewRequest > MS_BETWEEN_REQUESTS) {
- Log.w(TAG, "Delay to next request (" + delayToNewRequest
- + ") is longer than expected. Resetting to now.");
- mTimestampForNewRequest = currentTime;
- }
-
- // Confirm that the timestamp for the next POST is less than the current delay.
- long delayToNextPost = mTimestampForNextPostAttempt - currentTime;
- long lastGeneratedDelay = scheduler.getGeneratedDelay();
- if (delayToNextPost > lastGeneratedDelay) {
- Log.w(TAG, "Delay to next post attempt (" + delayToNextPost
- + ") is greater than expected (" + lastGeneratedDelay
- + "). Resetting to now.");
- mTimestampForNextPostAttempt = currentTime;
- }
-
- migrateToNewerChromeVersions();
- mStateHasBeenRestored = true;
- }
-
- /**
- * Writes out the current state to a file.
- */
- private void saveState(Context context) {
- SharedPreferences prefs = OmahaBase.getSharedPreferences(context);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(OmahaBase.PREF_SEND_INSTALL_EVENT, mSendInstallEvent);
- editor.putLong(OmahaBase.PREF_TIMESTAMP_OF_INSTALL, mTimestampOfInstall);
- editor.putLong(
- OmahaBase.PREF_TIMESTAMP_FOR_NEXT_POST_ATTEMPT, mTimestampForNextPostAttempt);
- editor.putLong(OmahaBase.PREF_TIMESTAMP_FOR_NEW_REQUEST, mTimestampForNewRequest);
- editor.putLong(OmahaBase.PREF_TIMESTAMP_OF_REQUEST,
- hasRequest() ? mCurrentRequest.getCreationTimestamp() : INVALID_TIMESTAMP);
- editor.putString(OmahaBase.PREF_PERSISTED_REQUEST_ID,
- hasRequest() ? mCurrentRequest.getRequestID() : INVALID_REQUEST_ID);
- editor.putString(
- OmahaBase.PREF_LATEST_VERSION, mLatestVersion == null ? "" : mLatestVersion);
- editor.putString(OmahaBase.PREF_MARKET_URL, mMarketURL == null ? "" : mMarketURL);
- editor.putString(OmahaBase.PREF_INSTALL_SOURCE, mInstallSource);
- editor.apply();
-
- mDelegate.onSaveStateDone(mTimestampForNewRequest, mTimestampForNextPostAttempt);
- }
-
- private void migrateToNewerChromeVersions() {
- // Remove any repeating alarms in favor of the new scheduling setup on M58 and up.
- // Seems cheaper to cancel the alarm repeatedly than to store a SharedPreference and never
- // do it again.
- Intent intent = new Intent(getContext(), OmahaClient.class);
- intent.setAction(ACTION_REGISTER_REQUEST);
- getBackoffScheduler().cancelAlarm(intent);
- }
-
- Context getContext() {
- return mDelegate.getContext();
- }
-
- private RequestGenerator getRequestGenerator() {
- return mDelegate.getRequestGenerator();
- }
-
- private ExponentialBackoffScheduler getBackoffScheduler() {
- return mDelegate.getScheduler();
- }
-
- void setDelegateForTests(OmahaDelegate delegate) {
- mDelegate = delegate;
- }
}

Powered by Google App Engine
This is Rietveld 408576698