| 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;
|
| - }
|
| }
|
|
|