Commit ded86454 authored by Andrey Zaytsev's avatar Andrey Zaytsev Committed by Commit Bot

Omaha on Android: added an on-demand update check

Bug: 1070620
Change-Id: I3b61e067b09c70c555ccd37bb048d8b78bba9f0e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2241682Reviewed-by: default avatarJoshua Pawlicki <waffles@chromium.org>
Commit-Queue: Andrey Zaytsev <andzaytsev@google.com>
Cr-Commit-Position: refs/heads/master@{#781315}
parent 4ebd1b3c
......@@ -17,6 +17,7 @@ import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.StreamUtil;
import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.ChromeVersionInfo;
import java.io.BufferedOutputStream;
......@@ -72,6 +73,17 @@ public class OmahaBase {
}
}
/** Represents the status of a manually-triggered update check. */
@IntDef({UpdateStatus.UPDATED, UpdateStatus.OUTDATED, UpdateStatus.OFFLINE,
UpdateStatus.FAILED})
@Retention(RetentionPolicy.SOURCE)
@interface UpdateStatus {
int UPDATED = 0;
int OUTDATED = 1;
int OFFLINE = 2;
int FAILED = 3;
}
// Flags for retrieving the OmahaClient's state after it's written to disk.
// The PREF_PACKAGE doesn't match the current OmahaClient package for historical reasons.
static final String PREF_PACKAGE = "com.google.android.apps.chrome.omaha";
......@@ -135,6 +147,9 @@ public class OmahaBase {
protected VersionConfig mVersionConfig;
protected boolean mSendInstallEvent;
// Request failure error code.
private int mRequestErrorCode;
/** See {@link #sIsDisabled}. */
public static void setIsDisabledForTesting(boolean state) {
sIsDisabled = state;
......@@ -153,6 +168,40 @@ public class OmahaBase {
mDelegate = delegate;
}
/**
* Synchronously checks for updates.
* @return UpdateStatus enum value corresponding to the update state.
*/
public @UpdateStatus int checkForUpdates() {
// Since this update check is synchronous and blocking on the network
// connection, it should not be run on the UI thread.
assert !ThreadUtils.runningOnUiThread();
// Create all the metadata needed for an Omaha request.
long currentTimestamp = getBackoffScheduler().getCurrentTime();
String installSource =
mDelegate.isInSystemImage() ? INSTALL_SOURCE_SYSTEM : INSTALL_SOURCE_ORGANIC;
RequestData currentRequest =
createRequestData(false, currentTimestamp, null, installSource);
String sessionID = mDelegate.generateUUID();
long timestampOfInstall = OmahaBase.getSharedPreferences().getLong(
OmahaBase.PREF_TIMESTAMP_OF_INSTALL, currentTimestamp);
// Send the request and parse the response.
VersionConfig versionConfig = generateAndPostRequest(
currentTimestamp, sessionID, currentRequest, timestampOfInstall);
if (versionConfig == null) {
return (mRequestErrorCode == RequestFailureException.ERROR_CONNECTIVITY)
? UpdateStatus.OFFLINE
: UpdateStatus.FAILED;
}
// Compare the current version with the latest received from the server.
VersionNumber current = VersionNumber.fromString(getInstalledVersion());
VersionNumber latest = VersionNumber.fromString(versionConfig.latestVersion);
if (current == null || latest == null) {
return UpdateStatus.FAILED;
}
return current.isSmallerThan(latest) ? UpdateStatus.OUTDATED : UpdateStatus.UPDATED;
}
protected void run() {
if (OmahaBase.isDisabled() || getRequestGenerator() == null) {
Log.v(TAG, "Disabled. Ignoring intent.");
......@@ -220,6 +269,7 @@ public class OmahaBase {
String sessionID = mDelegate.generateUUID();
boolean sendingInstallRequest = mSendInstallEvent;
boolean succeeded = generateAndPostRequest(currentTimestamp, sessionID);
onResponseReceived(succeeded);
if (succeeded && sendingInstallRequest) {
// Only the first request ever generated should contain an install event.
......@@ -229,6 +279,9 @@ public class OmahaBase {
// Create and immediately send another request for a ping and update check.
registerNewRequest(currentTimestamp);
succeeded &= generateAndPostRequest(currentTimestamp, sessionID);
// Previous line is executed only when succeeded is true, so the updated value
// reflects the status of the last call.
onResponseReceived(succeeded);
}
result = succeeded ? PostResult.SENT : PostResult.FAILED;
......@@ -240,32 +293,42 @@ public class OmahaBase {
return result;
}
/**
* @return version currently installed on the device.
*/
protected String getInstalledVersion() {
return VersionNumberGetter.getInstance().getCurrentlyUsedVersion(getContext());
}
protected boolean generateAndPostRequest(long currentTimestamp, String sessionID) {
boolean succeeded = false;
return generateAndPostRequest(
currentTimestamp, sessionID, mCurrentRequest, mTimestampOfInstall)
!= null;
}
protected VersionConfig generateAndPostRequest(long currentTimestamp, String sessionID,
RequestData currentRequest, long timestampOfInstall) {
try {
// Generate the XML for the current request.
long installAgeInDays = RequestGenerator.installAge(
currentTimestamp, mTimestampOfInstall, mCurrentRequest.isSendInstallEvent());
String version =
VersionNumberGetter.getInstance().getCurrentlyUsedVersion(getContext());
String xml = getRequestGenerator().generateXML(sessionID, version, installAgeInDays,
currentTimestamp, timestampOfInstall, currentRequest.isSendInstallEvent());
String xml = getRequestGenerator().generateXML(sessionID, getInstalledVersion(),
installAgeInDays,
mVersionConfig == null ? UNKNOWN_DATE : mVersionConfig.serverDate,
mCurrentRequest);
currentRequest);
// 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);
mVersionConfig = parser.parseResponse(response);
succeeded = true;
ResponseParser parser = new ResponseParser(appId, currentRequest.isSendInstallEvent());
return parser.parseResponse(response);
} catch (RequestFailureException e) {
Log.e(TAG, "Failed to contact server: ", e);
mRequestErrorCode = e.errorCode;
return null;
}
return onResponseReceived(succeeded);
}
protected boolean onResponseReceived(boolean succeeded) {
......@@ -309,6 +372,11 @@ public class OmahaBase {
}
private RequestData createRequestData(long currentTimestamp, String persistedID) {
return createRequestData(mSendInstallEvent, currentTimestamp, persistedID, mInstallSource);
}
private RequestData createRequestData(boolean sendInstallEvent, long currentTimestamp,
String persistedID, String installSource) {
// 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)) {
......@@ -316,7 +384,7 @@ public class OmahaBase {
} else {
requestID = persistedID;
}
return new RequestData(mSendInstallEvent, currentTimestamp, requestID, mInstallSource);
return new RequestData(sendInstallEvent, currentTimestamp, requestID, installSource);
}
private boolean hasRequest() {
......@@ -374,7 +442,8 @@ public class OmahaBase {
} 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);
throw new RequestFailureException("Failed to open connection to URL", e,
RequestFailureException.ERROR_CONNECTIVITY);
}
}
......@@ -500,7 +569,8 @@ public class OmahaBase {
StreamUtil.closeQuietly(writer);
checkServerResponseCode(urlConnection);
} catch (IOException | SecurityException | ArrayIndexOutOfBoundsException e) {
throw new RequestFailureException("Failed to write request to server: ", e);
throw new RequestFailureException("Failed to write request to server: ", e,
RequestFailureException.ERROR_CONNECTIVITY);
}
try {
......@@ -517,7 +587,8 @@ public class OmahaBase {
StreamUtil.closeQuietly(in);
}
} catch (IOException e) {
throw new RequestFailureException("Failed when reading response from server: ", e);
throw new RequestFailureException("Failed when reading response from server: ", e,
RequestFailureException.ERROR_CONNECTIVITY);
}
}
......
......@@ -19,6 +19,7 @@ public class RequestFailureException extends Exception {
public static final int ERROR_PARSE_URLS = 8;
public static final int ERROR_PARSE_UPDATECHECK = 9;
public static final int ERROR_PARSE_MANIFEST = 10;
public static final int ERROR_CONNECTIVITY = 11;
public int errorCode = ERROR_UNDEFINED;
......
......@@ -69,6 +69,10 @@ public class ResponseParser {
private boolean mParsedPing;
private boolean mParsedUpdatecheck;
public ResponseParser(String appId, boolean expectInstallEvent) {
this(appId, expectInstallEvent, !expectInstallEvent, !expectInstallEvent);
}
public ResponseParser(String appId, boolean expectInstallEvent, boolean expectPing,
boolean expectUpdatecheck) {
this(false, appId, expectInstallEvent, expectPing, expectUpdatecheck);
......
......@@ -203,12 +203,17 @@ public class OmahaBaseTest {
private final boolean mConnectionTimesOut;
private final boolean mIsOnTablet;
private String mUpdateVersion;
private String mInstalledVersion;
public MockOmahaBase(OmahaDelegate delegate, @ServerResponse int serverResponse,
@ConnectionStatus int connectionStatus, DeviceType deviceType) {
super(delegate);
mSendValidResponse = serverResponse == ServerResponse.SUCCESS;
mConnectionTimesOut = connectionStatus == ConnectionStatus.TIMES_OUT;
mIsOnTablet = deviceType == DeviceType.TABLET;
mUpdateVersion = "1.2.3.4";
mInstalledVersion = "1.2.3.4";
}
/**
......@@ -240,19 +245,32 @@ public class OmahaBaseTest {
mSendInstallEvent = state;
}
public void setUpdateVersion(String version) {
mUpdateVersion = version;
}
public void setInstalledVersion(String version) {
mInstalledVersion = version;
}
@Override
protected HttpURLConnection createConnection() {
MockConnection connection = null;
try {
URL url = new URL(mDelegate.getRequestGenerator().getServerUrl());
connection = new MockConnection(url, mIsOnTablet, mSendValidResponse,
mSendInstallEvent, mConnectionTimesOut);
mSendInstallEvent, mConnectionTimesOut, mUpdateVersion);
mMockConnections.addLast(connection);
} catch (MalformedURLException e) {
Assert.fail("Caught a malformed URL exception: " + e);
}
return connection;
}
@Override
protected String getInstalledVersion() {
return mInstalledVersion;
}
}
@Test
......@@ -565,6 +583,97 @@ public class OmahaBaseTest {
mDelegate.mTimestampsOnSaveState);
}
@Test
@SmallTest
@Feature({"Omaha"})
public void testCheckForUpdatesConnectionTimesOut() {
final long now = 10000L;
mDelegate = new MockOmahaDelegate(mContext, DeviceType.HANDSET, InstallSource.ORGANIC);
mDelegate.getScheduler().setCurrentTime(now);
mOmahaBase = createOmahaBase(
ServerResponse.FAILURE, ConnectionStatus.TIMES_OUT, DeviceType.HANDSET);
@OmahaBase.UpdateStatus
int status = mOmahaBase.checkForUpdates();
Assert.assertEquals(OmahaBase.UpdateStatus.OFFLINE, status);
}
@Test
@SmallTest
@Feature({"Omaha"})
public void testCheckForUpdatesUpdated() {
final long now = 10000L;
final String version = "89.0.12.5342";
mDelegate = new MockOmahaDelegate(mContext, DeviceType.HANDSET, InstallSource.ORGANIC);
mDelegate.getScheduler().setCurrentTime(now);
mOmahaBase = createOmahaBase();
mOmahaBase.setInstalledVersion(version);
mOmahaBase.setUpdateVersion(version);
@OmahaBase.UpdateStatus
int status = mOmahaBase.checkForUpdates();
Assert.assertEquals(OmahaBase.UpdateStatus.UPDATED, status);
}
@Test
@SmallTest
@Feature({"Omaha"})
public void testCheckForUpdatesOutdated() {
final long now = 10000L;
final String oldVersion = "89.0.12.5342";
final String newVersion = "89.0.13.1242";
mDelegate = new MockOmahaDelegate(mContext, DeviceType.HANDSET, InstallSource.ORGANIC);
mDelegate.getScheduler().setCurrentTime(now);
mOmahaBase = createOmahaBase();
mOmahaBase.setInstalledVersion(oldVersion);
mOmahaBase.setUpdateVersion(newVersion);
@OmahaBase.UpdateStatus
int status = mOmahaBase.checkForUpdates();
Assert.assertEquals(OmahaBase.UpdateStatus.OUTDATED, status);
}
@Test
@SmallTest
@Feature({"Omaha"})
public void testCheckForUpdatesFailedIncorrectNewVersion() {
final long now = 10000L;
final String oldVersion = "89.0.12.5342";
final String newVersion = "Unknown";
mDelegate = new MockOmahaDelegate(mContext, DeviceType.HANDSET, InstallSource.ORGANIC);
mDelegate.getScheduler().setCurrentTime(now);
mOmahaBase = createOmahaBase();
mOmahaBase.setInstalledVersion(oldVersion);
mOmahaBase.setUpdateVersion(newVersion);
@OmahaBase.UpdateStatus
int status = mOmahaBase.checkForUpdates();
Assert.assertEquals(OmahaBase.UpdateStatus.FAILED, status);
}
@Test
@SmallTest
@Feature({"Omaha"})
public void testCheckForUpdatesFailedIncorrectOldVersion() {
final long now = 10000L;
final String oldVersion = "Unknown";
final String newVersion = "89.0.13.1242";
mDelegate = new MockOmahaDelegate(mContext, DeviceType.HANDSET, InstallSource.ORGANIC);
mDelegate.getScheduler().setCurrentTime(now);
mOmahaBase = createOmahaBase();
mOmahaBase.setInstalledVersion(oldVersion);
mOmahaBase.setUpdateVersion(newVersion);
@OmahaBase.UpdateStatus
int status = mOmahaBase.checkForUpdates();
Assert.assertEquals(OmahaBase.UpdateStatus.FAILED, status);
}
private void checkTimestamps(
long expectedRequestTimestamp, long expectedPostTimestamp, TimestampPair timestamps) {
Assert.assertEquals(expectedRequestTimestamp, timestamps.timestampNextRequest);
......@@ -580,13 +689,12 @@ public class OmahaBaseTest {
"https://market.android.com/details?id=com.google.android.apps.chrome";
private static final String MARKET_URL = STRIPPED_MARKET_URL + "/";
private static final String UPDATE_VERSION = "1.2.3.4";
// Parameters.
private final boolean mConnectionTimesOut;
private final ByteArrayInputStream mServerResponse;
private final ByteArrayOutputStream mOutputStream;
private final int mHTTPResponseCode;
private final String mUpdateVersion;
// Result variables.
private int mContentLength;
......@@ -597,10 +705,11 @@ public class OmahaBaseTest {
private String mRequestPropertyValue;
MockConnection(URL url, boolean usingTablet, boolean sendValidResponse,
boolean sendInstallEvent, boolean connectionTimesOut) {
boolean sendInstallEvent, boolean connectionTimesOut, String updateVersion) {
super(url);
Assert.assertEquals(MockRequestGenerator.SERVER_URL, url.toString());
mUpdateVersion = updateVersion;
String mockResponse = buildServerResponseString(usingTablet, sendInstallEvent);
mOutputStream = new ByteArrayOutputStream();
mServerResponse =
......@@ -632,7 +741,7 @@ public class OmahaBaseTest {
} else {
response += "<updatecheck status=\"ok\">";
response += "<urls><url codebase=\"" + MARKET_URL + "\"/></urls>";
response += "<manifest version=\"" + UPDATE_VERSION + "\">";
response += "<manifest version=\"" + mUpdateVersion + "\">";
response += "<packages>";
response += "<package hash=\"0\" name=\"dummy.apk\" required=\"true\" size=\"0\"/>";
response += "</packages>";
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment