| OLD | NEW |
| 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 } |
| OLD | NEW |