| 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.AlarmManager; | |
| 8 import android.app.IntentService; | 7 import android.app.IntentService; |
| 9 import android.app.PendingIntent; | |
| 10 import android.content.Context; | 8 import android.content.Context; |
| 11 import android.content.Intent; | 9 import android.content.Intent; |
| 12 import android.content.SharedPreferences; | 10 import android.content.SharedPreferences; |
| 13 import android.content.pm.ApplicationInfo; | |
| 14 import android.os.Build; | 11 import android.os.Build; |
| 15 import android.os.Looper; | 12 import android.support.annotation.IntDef; |
| 16 | 13 |
| 17 import org.chromium.base.ApiCompatibilityUtils; | |
| 18 import org.chromium.base.ApplicationStatus; | |
| 19 import org.chromium.base.Log; | 14 import org.chromium.base.Log; |
| 15 import org.chromium.base.ThreadUtils; |
| 20 import org.chromium.base.VisibleForTesting; | 16 import org.chromium.base.VisibleForTesting; |
| 21 import org.chromium.chrome.browser.ChromeApplication; | |
| 22 | 17 |
| 23 import java.io.IOException; | 18 import java.io.IOException; |
| 19 import java.lang.annotation.Retention; |
| 20 import java.lang.annotation.RetentionPolicy; |
| 24 import java.net.HttpURLConnection; | 21 import java.net.HttpURLConnection; |
| 25 import java.net.MalformedURLException; | 22 import java.net.MalformedURLException; |
| 26 import java.net.URL; | 23 import java.net.URL; |
| 27 import java.util.UUID; | |
| 28 import java.util.concurrent.TimeUnit; | 24 import java.util.concurrent.TimeUnit; |
| 29 | 25 |
| 30 /** | 26 /** |
| 31 * Keeps tabs on the current state of Chrome, tracking if and when a request sho
uld be sent to the | 27 * Keeps tabs on the current state of Chrome, tracking if and when a request sho
uld be sent to the |
| 32 * Omaha Server. | 28 * Omaha Server. |
| 33 * | 29 * |
| 34 * A hook in ChromeActivity's doDeferredResume() initializes the service. Furth
er attempts to | 30 * A hook in ChromeActivity's doDeferredResume() initializes the service. Furth
er attempts to |
| 35 * reschedule events will be scheduled by the class itself. | 31 * reschedule events will be scheduled by the class itself. |
| 36 * | 32 * |
| 37 * Each request to the server will perform an update check and ping the server. | 33 * Each request to the server will perform an update check and ping the server. |
| (...skipping 10 matching lines...) Expand all Loading... |
| 48 * handled in onCreate(). | 44 * handled in onCreate(). |
| 49 * | 45 * |
| 50 * Implementation notes: | 46 * Implementation notes: |
| 51 * http://docs.google.com/a/google.com/document/d/1scTCovqASf5ktkOeVj8wFRkWTCeDY
w2LrOBNn05CDB0/edit | 47 * http://docs.google.com/a/google.com/document/d/1scTCovqASf5ktkOeVj8wFRkWTCeDY
w2LrOBNn05CDB0/edit |
| 52 * | 48 * |
| 53 * NOTE: This class can never be renamed because the user may have Intents float
ing around that | 49 * NOTE: This class can never be renamed because the user may have Intents float
ing around that |
| 54 * reference this class specifically. | 50 * reference this class specifically. |
| 55 */ | 51 */ |
| 56 public class OmahaClient extends IntentService { | 52 public class OmahaClient extends IntentService { |
| 57 // Results of {@link #handlePostRequest()}. | 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 {} |
| 58 static final int POST_RESULT_NO_REQUEST = 0; | 57 static final int POST_RESULT_NO_REQUEST = 0; |
| 59 static final int POST_RESULT_SENT = 1; | 58 static final int POST_RESULT_SENT = 1; |
| 60 static final int POST_RESULT_FAILED = 2; | 59 static final int POST_RESULT_FAILED = 2; |
| 61 static final int POST_RESULT_SCHEDULED = 3; | 60 static final int POST_RESULT_SCHEDULED = 3; |
| 62 | 61 |
| 63 private static final String TAG = "omaha"; | 62 private static final String TAG = "omaha"; |
| 64 | 63 |
| 65 // Intent actions. | 64 /** Deprecated; kept around to cancel alarms set for OmahaClient pre-M58. */ |
| 66 private static final String ACTION_INITIALIZE = | |
| 67 "org.chromium.chrome.browser.omaha.ACTION_INITIALIZE"; | |
| 68 private static final String ACTION_REGISTER_REQUEST = | 65 private static final String ACTION_REGISTER_REQUEST = |
| 69 "org.chromium.chrome.browser.omaha.ACTION_REGISTER_REQUEST"; | 66 "org.chromium.chrome.browser.omaha.ACTION_REGISTER_REQUEST"; |
| 70 private static final String ACTION_POST_REQUEST = | |
| 71 "org.chromium.chrome.browser.omaha.ACTION_POST_REQUEST"; | |
| 72 | 67 |
| 73 // Delays between events. | 68 // Delays between events. |
| 74 private static final long MS_POST_BASE_DELAY = TimeUnit.HOURS.toMillis(1); | 69 static final long MS_POST_BASE_DELAY = TimeUnit.HOURS.toMillis(1); |
| 75 private static final long MS_POST_MAX_DELAY = TimeUnit.HOURS.toMillis(5); | 70 static final long MS_POST_MAX_DELAY = TimeUnit.HOURS.toMillis(5); |
| 76 static final long MS_BETWEEN_REQUESTS = TimeUnit.HOURS.toMillis(5); | 71 static final long MS_BETWEEN_REQUESTS = TimeUnit.HOURS.toMillis(5); |
| 77 private static final int MS_CONNECTION_TIMEOUT = (int) TimeUnit.MINUTES.toMi
llis(1); | 72 static final int MS_CONNECTION_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(1); |
| 78 | 73 |
| 79 // Strings indicating how the Chrome APK arrived on the user's device. These
values MUST NOT | 74 // Strings indicating how the Chrome APK arrived on the user's device. These
values MUST NOT |
| 80 // be changed without updating the corresponding Omaha server strings. | 75 // be changed without updating the corresponding Omaha server strings. |
| 81 static final String INSTALL_SOURCE_SYSTEM = "system_image"; | 76 static final String INSTALL_SOURCE_SYSTEM = "system_image"; |
| 82 static final String INSTALL_SOURCE_ORGANIC = "organic"; | 77 static final String INSTALL_SOURCE_ORGANIC = "organic"; |
| 83 | 78 |
| 84 private static final long INVALID_TIMESTAMP = -1; | 79 private static final long INVALID_TIMESTAMP = -1; |
| 85 @VisibleForTesting | 80 @VisibleForTesting |
| 86 static final String INVALID_REQUEST_ID = "invalid"; | 81 static final String INVALID_REQUEST_ID = "invalid"; |
| 87 | 82 |
| 88 // Member fields not persisted to disk. | 83 // Member fields not persisted to disk. |
| 89 private boolean mStateHasBeenRestored; | 84 private boolean mStateHasBeenRestored; |
| 90 private ExponentialBackoffScheduler mBackoffScheduler; | 85 private OmahaDelegate mDelegate; |
| 91 private RequestGenerator mGenerator; | |
| 92 | 86 |
| 93 // State saved written to and read from disk. | 87 // State saved written to and read from disk. |
| 94 private RequestData mCurrentRequest; | 88 private RequestData mCurrentRequest; |
| 95 private long mTimestampOfInstall; | 89 private long mTimestampOfInstall; |
| 96 private long mTimestampForNextPostAttempt; | 90 private long mTimestampForNextPostAttempt; |
| 97 private long mTimestampForNewRequest; | 91 private long mTimestampForNewRequest; |
| 98 private String mLatestVersion; | 92 private String mLatestVersion; |
| 99 private String mMarketURL; | 93 private String mMarketURL; |
| 100 private String mInstallSource; | 94 private String mInstallSource; |
| 101 protected boolean mSendInstallEvent; | 95 protected boolean mSendInstallEvent; |
| 102 | 96 |
| 103 public OmahaClient() { | 97 public OmahaClient() { |
| 104 super(TAG); | 98 super(TAG); |
| 105 setIntentRedelivery(true); | 99 setIntentRedelivery(true); |
| 106 } | 100 } |
| 107 | 101 |
| 108 @VisibleForTesting | |
| 109 long getTimestampForNextPostAttempt() { | |
| 110 return mTimestampForNextPostAttempt; | |
| 111 } | |
| 112 | |
| 113 @VisibleForTesting | |
| 114 long getTimestampForNewRequest() { | |
| 115 return mTimestampForNewRequest; | |
| 116 } | |
| 117 | |
| 118 @VisibleForTesting | |
| 119 int getCumulativeFailedAttempts() { | |
| 120 return getBackoffScheduler().getNumFailedAttempts(); | |
| 121 } | |
| 122 | |
| 123 /** | |
| 124 * Creates the scheduler used to space out POST attempts. | |
| 125 */ | |
| 126 @VisibleForTesting | |
| 127 ExponentialBackoffScheduler createBackoffScheduler(String prefPackage, Conte
xt context, | |
| 128 long base, long max) { | |
| 129 return new ExponentialBackoffScheduler(prefPackage, context, base, max); | |
| 130 } | |
| 131 | |
| 132 /** | |
| 133 * Creates the request generator used to create Omaha XML. | |
| 134 */ | |
| 135 @VisibleForTesting | |
| 136 RequestGenerator createRequestGenerator(Context context) { | |
| 137 return ((ChromeApplication) context.getApplicationContext()).createOmaha
RequestGenerator(); | |
| 138 } | |
| 139 | |
| 140 /** | 102 /** |
| 141 * Handles an action on a thread separate from the UI thread. | 103 * Handles an action on a thread separate from the UI thread. |
| 142 * @param intent Intent fired by some part of Chrome. | 104 * @param intent Intent fired by some part of Chrome. |
| 143 */ | 105 */ |
| 144 @Override | 106 @Override |
| 145 public void onHandleIntent(Intent intent) { | 107 public void onHandleIntent(Intent intent) { |
| 146 assert Looper.myLooper() != Looper.getMainLooper(); | 108 assert !ThreadUtils.runningOnUiThread(); |
| 109 run(); |
| 110 } |
| 147 | 111 |
| 112 protected void run() { |
| 148 if (OmahaBase.isDisabled() || Build.VERSION.SDK_INT > Build.VERSION_CODE
S.N | 113 if (OmahaBase.isDisabled() || Build.VERSION.SDK_INT > Build.VERSION_CODE
S.N |
| 149 || getRequestGenerator() == null) { | 114 || getRequestGenerator() == null) { |
| 150 Log.v(TAG, "Disabled. Ignoring intent."); | 115 Log.v(TAG, "Disabled. Ignoring intent."); |
| 151 return; | 116 return; |
| 152 } | 117 } |
| 153 | 118 |
| 154 restoreState(this); | 119 if (mDelegate == null) mDelegate = new OmahaDelegateImpl(this); |
| 155 | 120 |
| 156 if (ACTION_INITIALIZE.equals(intent.getAction())) { | 121 restoreState(getContext()); |
| 157 handleInitialize(); | 122 |
| 158 } else if (ACTION_REGISTER_REQUEST.equals(intent.getAction())) { | 123 long nextTimestamp = Long.MAX_VALUE; |
| 159 handleRegisterRequest(); | 124 if (mDelegate.isChromeBeingUsed()) { |
| 160 } else if (ACTION_POST_REQUEST.equals(intent.getAction())) { | 125 handleRegisterActiveRequest(); |
| 161 handlePostRequest(); | 126 nextTimestamp = Math.min(nextTimestamp, mTimestampForNewRequest); |
| 162 } else { | |
| 163 Log.e(TAG, "Got unknown action from intent: " + intent.getAction()); | |
| 164 } | 127 } |
| 165 | 128 |
| 166 saveState(this); | 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()); |
| 167 } | 142 } |
| 168 | 143 |
| 169 /** | 144 /** |
| 170 * Begin communicating with the Omaha Update Server. | 145 * Begin communicating with the Omaha Update Server. |
| 171 */ | 146 */ |
| 172 static void startService(Context context) { | 147 static void startService(Context context) { |
| 173 Intent omahaIntent = createInitializeIntent(context); | 148 context.startService(createOmahaIntent(context)); |
| 174 context.startService(omahaIntent); | |
| 175 } | 149 } |
| 176 | 150 |
| 177 static Intent createInitializeIntent(Context context) { | 151 static Intent createOmahaIntent(Context context) { |
| 178 Intent intent = new Intent(context, OmahaClient.class); | 152 return new Intent(context, OmahaClient.class); |
| 179 intent.setAction(ACTION_INITIALIZE); | |
| 180 return intent; | |
| 181 } | |
| 182 | |
| 183 /** | |
| 184 * Start a recurring alarm to fire request generation intents. | |
| 185 */ | |
| 186 private void handleInitialize() { | |
| 187 scheduleActiveUserCheck(); | |
| 188 | |
| 189 // If a request exists, kick off POSTing it to the server immediately. | |
| 190 if (hasRequest()) handlePostRequest(); | |
| 191 } | |
| 192 | |
| 193 /** | |
| 194 * Returns an Intent for registering a new request to send to the server. | |
| 195 */ | |
| 196 static Intent createRegisterRequestIntent(Context context) { | |
| 197 Intent intent = new Intent(context, OmahaClient.class); | |
| 198 intent.setAction(ACTION_REGISTER_REQUEST); | |
| 199 return intent; | |
| 200 } | 153 } |
| 201 | 154 |
| 202 /** | 155 /** |
| 203 * Determines if a new request should be generated. New requests are only g
enerated if enough | 156 * Determines if a new request should be generated. New requests are only g
enerated if enough |
| 204 * time has passed between now and the last time a request was generated. | 157 * time has passed between now and the last time a request was generated. |
| 205 */ | 158 */ |
| 206 private void handleRegisterRequest() { | 159 private void handleRegisterActiveRequest() { |
| 207 if (!isChromeBeingUsed()) { | |
| 208 getBackoffScheduler().cancelAlarm(createRegisterRequestIntent(this))
; | |
| 209 return; | |
| 210 } | |
| 211 | |
| 212 // If the current request is too old, generate a new one. | 160 // If the current request is too old, generate a new one. |
| 213 long currentTimestamp = getBackoffScheduler().getCurrentTime(); | 161 long currentTimestamp = getBackoffScheduler().getCurrentTime(); |
| 214 boolean isTooOld = hasRequest() | 162 boolean isTooOld = hasRequest() |
| 215 && mCurrentRequest.getAgeInMilliseconds(currentTimestamp) >= MS_
BETWEEN_REQUESTS; | 163 && mCurrentRequest.getAgeInMilliseconds(currentTimestamp) >= MS_
BETWEEN_REQUESTS; |
| 216 boolean isOverdue = !hasRequest() && currentTimestamp >= mTimestampForNe
wRequest; | 164 boolean isOverdue = currentTimestamp >= mTimestampForNewRequest; |
| 217 if (isTooOld || isOverdue) { | 165 if (isTooOld || isOverdue) { |
| 218 registerNewRequest(currentTimestamp); | 166 registerNewRequest(currentTimestamp); |
| 219 } | 167 } |
| 220 | |
| 221 // Send the request. | |
| 222 if (hasRequest()) { | |
| 223 handlePostRequest(); | |
| 224 } | |
| 225 } | |
| 226 | |
| 227 /** | |
| 228 * Returns an Intent for POSTing the current request to the Omaha server. | |
| 229 */ | |
| 230 static Intent createPostRequestIntent(Context context) { | |
| 231 Intent intent = new Intent(context, OmahaClient.class); | |
| 232 intent.setAction(ACTION_POST_REQUEST); | |
| 233 return intent; | |
| 234 } | 168 } |
| 235 | 169 |
| 236 /** | 170 /** |
| 237 * Sends the request it is holding. | 171 * Sends the request it is holding. |
| 238 */ | 172 */ |
| 239 @VisibleForTesting | 173 private int handlePostRequest() { |
| 240 protected int handlePostRequest() { | 174 if (!hasRequest()) { |
| 241 if (!hasRequest()) return POST_RESULT_NO_REQUEST; | 175 mDelegate.onHandlePostRequestDone(POST_RESULT_NO_REQUEST, false); |
| 176 return POST_RESULT_NO_REQUEST; |
| 177 } |
| 242 | 178 |
| 243 // If enough time has passed since the last attempt, try sending a reque
st. | 179 // If enough time has passed since the last attempt, try sending a reque
st. |
| 244 int result; | 180 int result; |
| 245 long currentTimestamp = getBackoffScheduler().getCurrentTime(); | 181 long currentTimestamp = getBackoffScheduler().getCurrentTime(); |
| 182 boolean installEventWasSent = false; |
| 246 if (currentTimestamp >= mTimestampForNextPostAttempt) { | 183 if (currentTimestamp >= mTimestampForNextPostAttempt) { |
| 247 // All requests made during the same session should have the same ID
. | 184 // All requests made during the same session should have the same ID
. |
| 248 String sessionID = generateRandomUUID(); | 185 String sessionID = mDelegate.generateUUID(); |
| 249 boolean sendingInstallRequest = mSendInstallEvent; | 186 boolean sendingInstallRequest = mSendInstallEvent; |
| 250 boolean succeeded = generateAndPostRequest(currentTimestamp, session
ID); | 187 boolean succeeded = generateAndPostRequest(currentTimestamp, session
ID); |
| 251 | 188 |
| 252 if (succeeded && sendingInstallRequest) { | 189 if (succeeded && sendingInstallRequest) { |
| 253 // Only the first request ever generated should contain an insta
ll event. | 190 // Only the first request ever generated should contain an insta
ll event. |
| 254 mSendInstallEvent = false; | 191 mSendInstallEvent = false; |
| 192 installEventWasSent = true; |
| 255 | 193 |
| 256 // Create and immediately send another request for a ping and up
date check. | 194 // Create and immediately send another request for a ping and up
date check. |
| 257 registerNewRequest(currentTimestamp); | 195 registerNewRequest(currentTimestamp); |
| 258 generateAndPostRequest(currentTimestamp, sessionID); | 196 succeeded &= generateAndPostRequest(currentTimestamp, sessionID)
; |
| 259 } | 197 } |
| 260 | 198 |
| 261 result = succeeded ? POST_RESULT_SENT : POST_RESULT_FAILED; | 199 result = succeeded ? POST_RESULT_SENT : POST_RESULT_FAILED; |
| 262 } else { | 200 } else { |
| 263 schedulePost(); | |
| 264 result = POST_RESULT_SCHEDULED; | 201 result = POST_RESULT_SCHEDULED; |
| 265 } | 202 } |
| 266 | 203 |
| 204 mDelegate.onHandlePostRequestDone(result, installEventWasSent); |
| 267 return result; | 205 return result; |
| 268 } | 206 } |
| 269 | 207 |
| 270 /** Set an alarm to POST at the proper time. Previous alarms are destroyed.
*/ | |
| 271 private void schedulePost() { | |
| 272 Intent postIntent = createPostRequestIntent(this); | |
| 273 getBackoffScheduler().createAlarm(postIntent, mTimestampForNextPostAttem
pt); | |
| 274 } | |
| 275 | |
| 276 private boolean generateAndPostRequest(long currentTimestamp, String session
ID) { | 208 private boolean generateAndPostRequest(long currentTimestamp, String session
ID) { |
| 277 ExponentialBackoffScheduler scheduler = getBackoffScheduler(); | 209 ExponentialBackoffScheduler scheduler = getBackoffScheduler(); |
| 210 boolean succeeded = false; |
| 278 try { | 211 try { |
| 279 // Generate the XML for the current request. | 212 // Generate the XML for the current request. |
| 280 long installAgeInDays = RequestGenerator.installAge(currentTimestamp
, | 213 long installAgeInDays = RequestGenerator.installAge(currentTimestamp
, |
| 281 mTimestampOfInstall, mCurrentRequest.isSendInstallEvent()); | 214 mTimestampOfInstall, mCurrentRequest.isSendInstallEvent()); |
| 282 String version = VersionNumberGetter.getInstance().getCurrentlyUsedV
ersion(this); | 215 String version = VersionNumberGetter.getInstance().getCurrentlyUsedV
ersion(this); |
| 283 String xml = getRequestGenerator().generateXML( | 216 String xml = getRequestGenerator().generateXML( |
| 284 sessionID, version, installAgeInDays, mCurrentRequest); | 217 sessionID, version, installAgeInDays, mCurrentRequest); |
| 285 | 218 |
| 286 // Send the request to the server & wait for a response. | 219 // Send the request to the server & wait for a response. |
| 287 String response = postRequest(currentTimestamp, xml); | 220 String response = postRequest(currentTimestamp, xml); |
| 288 | 221 |
| 289 // Parse out the response. | 222 // Parse out the response. |
| 290 String appId = getRequestGenerator().getAppId(); | 223 String appId = getRequestGenerator().getAppId(); |
| 291 boolean sentPingAndUpdate = !mSendInstallEvent; | 224 boolean sentPingAndUpdate = !mSendInstallEvent; |
| 292 ResponseParser parser = new ResponseParser( | 225 ResponseParser parser = new ResponseParser( |
| 293 appId, mSendInstallEvent, sentPingAndUpdate, sentPingAndUpda
te); | 226 appId, mSendInstallEvent, sentPingAndUpdate, sentPingAndUpda
te); |
| 294 parser.parseResponse(response); | 227 parser.parseResponse(response); |
| 295 mLatestVersion = parser.getNewVersion(); | 228 mLatestVersion = parser.getNewVersion(); |
| 296 mMarketURL = parser.getURL(); | 229 mMarketURL = parser.getURL(); |
| 297 | 230 |
| 231 succeeded = true; |
| 232 } catch (RequestFailureException e) { |
| 233 Log.e(TAG, "Failed to contact server: ", e); |
| 234 } |
| 235 |
| 236 if (succeeded) { |
| 298 // If we've gotten this far, we've successfully sent a request. | 237 // If we've gotten this far, we've successfully sent a request. |
| 299 mCurrentRequest = null; | 238 mCurrentRequest = null; |
| 300 | 239 |
| 301 mTimestampForNewRequest = getBackoffScheduler().getCurrentTime() + M
S_BETWEEN_REQUESTS; | |
| 302 scheduleActiveUserCheck(); | |
| 303 | |
| 304 scheduler.resetFailedAttempts(); | 240 scheduler.resetFailedAttempts(); |
| 241 mTimestampForNewRequest = scheduler.getCurrentTime() + MS_BETWEEN_RE
QUESTS; |
| 305 mTimestampForNextPostAttempt = scheduler.calculateNextTimestamp(); | 242 mTimestampForNextPostAttempt = scheduler.calculateNextTimestamp(); |
| 306 Log.i(TAG, "Request to Server Successful. Timestamp for next request
:" | 243 Log.i(TAG, "Request to Server Successful. Timestamp for next request
:" |
| 307 + String.valueOf(mTimestampForNextPostAttempt)); | 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 } |
| 308 | 251 |
| 309 return true; | 252 mDelegate.onGenerateAndPostRequestDone(succeeded); |
| 310 } catch (RequestFailureException e) { | 253 return succeeded; |
| 311 // Set the alarm to try again later. | |
| 312 Log.e(TAG, "Failed to contact server: ", e); | |
| 313 scheduler.increaseFailedAttempts(); | |
| 314 mTimestampForNextPostAttempt = scheduler.calculateNextTimestamp(); | |
| 315 scheduler.createAlarm(createPostRequestIntent(this), mTimestampForNe
xtPostAttempt); | |
| 316 return false; | |
| 317 } | |
| 318 } | 254 } |
| 319 | 255 |
| 320 /** | 256 /** |
| 321 * Sets a repeating alarm that fires request registration Intents. | |
| 322 * Setting the alarm overwrites whatever alarm is already there, and rebooti
ng | |
| 323 * clears whatever alarms are currently set. | |
| 324 */ | |
| 325 private void scheduleActiveUserCheck() { | |
| 326 Intent registerIntent = createRegisterRequestIntent(this); | |
| 327 PendingIntent pIntent = PendingIntent.getService(this, 0, registerIntent
, 0); | |
| 328 AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE)
; | |
| 329 setAlarm(am, pIntent, mTimestampForNewRequest); | |
| 330 } | |
| 331 | |
| 332 /** | |
| 333 * Sets up a timer to fire after each interval. | |
| 334 * Override to prevent a real alarm from being set. | |
| 335 */ | |
| 336 @VisibleForTesting | |
| 337 protected void setAlarm(AlarmManager am, PendingIntent operation, long trigg
erAtTime) { | |
| 338 try { | |
| 339 am.setRepeating(AlarmManager.RTC, triggerAtTime, MS_BETWEEN_REQUESTS
, operation); | |
| 340 } catch (SecurityException e) { | |
| 341 Log.e(TAG, "Failed to set repeating alarm."); | |
| 342 } | |
| 343 } | |
| 344 | |
| 345 /** | |
| 346 * Determine whether or not Chrome is currently being used actively. | |
| 347 */ | |
| 348 @VisibleForTesting | |
| 349 protected boolean isChromeBeingUsed() { | |
| 350 boolean isChromeVisible = ApplicationStatus.hasVisibleActivities(); | |
| 351 boolean isScreenOn = ApiCompatibilityUtils.isInteractive(this); | |
| 352 return isChromeVisible && isScreenOn; | |
| 353 } | |
| 354 | |
| 355 /** | |
| 356 * Registers a new request with the current timestamp. Internal timestamps
are reset to start | 257 * Registers a new request with the current timestamp. Internal timestamps
are reset to start |
| 357 * fresh. | 258 * fresh. |
| 358 * @param currentTimestamp Current time. | 259 * @param currentTimestamp Current time. |
| 359 */ | 260 */ |
| 360 @VisibleForTesting | 261 private void registerNewRequest(long currentTimestamp) { |
| 361 void registerNewRequest(long currentTimestamp) { | |
| 362 mCurrentRequest = createRequestData(currentTimestamp, null); | 262 mCurrentRequest = createRequestData(currentTimestamp, null); |
| 363 getBackoffScheduler().resetFailedAttempts(); | 263 getBackoffScheduler().resetFailedAttempts(); |
| 364 mTimestampForNextPostAttempt = currentTimestamp; | 264 mTimestampForNextPostAttempt = currentTimestamp; |
| 365 | 265 |
| 366 // Tentatively set the timestamp for a new request. This will be update
d when the server | 266 // Tentatively set the timestamp for a new request. This will be update
d when the server |
| 367 // is successfully contacted. | 267 // is successfully contacted. |
| 368 mTimestampForNewRequest = currentTimestamp + MS_BETWEEN_REQUESTS; | 268 mTimestampForNewRequest = currentTimestamp + MS_BETWEEN_REQUESTS; |
| 369 scheduleActiveUserCheck(); | 269 |
| 270 mDelegate.onRegisterNewRequestDone(mTimestampForNewRequest, mTimestampFo
rNextPostAttempt); |
| 370 } | 271 } |
| 371 | 272 |
| 372 private RequestData createRequestData(long currentTimestamp, String persiste
dID) { | 273 private RequestData createRequestData(long currentTimestamp, String persiste
dID) { |
| 373 // If we're sending a persisted event, keep trying to send the same requ
est ID. | 274 // If we're sending a persisted event, keep trying to send the same requ
est ID. |
| 374 String requestID; | 275 String requestID; |
| 375 if (persistedID == null || INVALID_REQUEST_ID.equals(persistedID)) { | 276 if (persistedID == null || INVALID_REQUEST_ID.equals(persistedID)) { |
| 376 requestID = generateRandomUUID(); | 277 requestID = mDelegate.generateUUID(); |
| 377 } else { | 278 } else { |
| 378 requestID = persistedID; | 279 requestID = persistedID; |
| 379 } | 280 } |
| 380 return new RequestData(mSendInstallEvent, currentTimestamp, requestID, m
InstallSource); | 281 return new RequestData(mSendInstallEvent, currentTimestamp, requestID, m
InstallSource); |
| 381 } | 282 } |
| 382 | 283 |
| 383 @VisibleForTesting | 284 private boolean hasRequest() { |
| 384 boolean hasRequest() { | |
| 385 return mCurrentRequest != null; | 285 return mCurrentRequest != null; |
| 386 } | 286 } |
| 387 | 287 |
| 388 /** | 288 /** |
| 389 * Posts the request to the Omaha server. | 289 * Posts the request to the Omaha server. |
| 390 * @return the XML response as a String. | 290 * @return the XML response as a String. |
| 391 * @throws RequestFailureException if the request fails. | 291 * @throws RequestFailureException if the request fails. |
| 392 */ | 292 */ |
| 393 @VisibleForTesting | 293 private String postRequest(long timestamp, String xml) throws RequestFailure
Exception { |
| 394 String postRequest(long timestamp, String xml) throws RequestFailureExceptio
n { | |
| 395 String response = null; | 294 String response = null; |
| 396 | 295 |
| 397 HttpURLConnection urlConnection = null; | 296 HttpURLConnection urlConnection = null; |
| 398 try { | 297 try { |
| 399 urlConnection = createConnection(); | 298 urlConnection = createConnection(); |
| 400 | 299 |
| 401 // Prepare the HTTP header. | 300 // Prepare the HTTP header. |
| 402 urlConnection.setDoOutput(true); | 301 urlConnection.setDoOutput(true); |
| 403 urlConnection.setFixedLengthStreamingMode(xml.getBytes().length); | 302 urlConnection.setFixedLengthStreamingMode(xml.getBytes().length); |
| 404 if (mSendInstallEvent && getCumulativeFailedAttempts() > 0) { | 303 if (mSendInstallEvent && getBackoffScheduler().getNumFailedAttempts(
) > 0) { |
| 405 String age = Long.toString(mCurrentRequest.getAgeInSeconds(times
tamp)); | 304 String age = Long.toString(mCurrentRequest.getAgeInSeconds(times
tamp)); |
| 406 urlConnection.addRequestProperty("X-RequestAge", age); | 305 urlConnection.addRequestProperty("X-RequestAge", age); |
| 407 } | 306 } |
| 408 | 307 |
| 409 response = OmahaBase.sendRequestToServer(urlConnection, xml); | 308 response = OmahaBase.sendRequestToServer(urlConnection, xml); |
| 410 } catch (IllegalAccessError e) { | 309 } catch (IllegalAccessError e) { |
| 411 throw new RequestFailureException("Caught an IllegalAccessError:", e
); | 310 throw new RequestFailureException("Caught an IllegalAccessError:", e
); |
| 412 } catch (IllegalArgumentException e) { | 311 } catch (IllegalArgumentException e) { |
| 413 throw new RequestFailureException("Caught an IllegalArgumentExceptio
n:", e); | 312 throw new RequestFailureException("Caught an IllegalArgumentExceptio
n:", e); |
| 414 } catch (IllegalStateException e) { | 313 } catch (IllegalStateException e) { |
| (...skipping 19 matching lines...) Expand all Loading... |
| 434 connection.setReadTimeout(MS_CONNECTION_TIMEOUT); | 333 connection.setReadTimeout(MS_CONNECTION_TIMEOUT); |
| 435 return connection; | 334 return connection; |
| 436 } catch (MalformedURLException e) { | 335 } catch (MalformedURLException e) { |
| 437 throw new RequestFailureException("Caught a malformed URL exception.
", e); | 336 throw new RequestFailureException("Caught a malformed URL exception.
", e); |
| 438 } catch (IOException e) { | 337 } catch (IOException e) { |
| 439 throw new RequestFailureException("Failed to open connection to URL"
, e); | 338 throw new RequestFailureException("Failed to open connection to URL"
, e); |
| 440 } | 339 } |
| 441 } | 340 } |
| 442 | 341 |
| 443 /** | 342 /** |
| 444 * Determine how the Chrome APK arrived on the device. | |
| 445 * @param context Context to pull resources from. | |
| 446 * @return A String indicating the install source. | |
| 447 */ | |
| 448 String determineInstallSource() { | |
| 449 boolean isInSystemImage = (getApplicationFlags() & ApplicationInfo.FLAG_
SYSTEM) != 0; | |
| 450 return isInSystemImage ? INSTALL_SOURCE_SYSTEM : INSTALL_SOURCE_ORGANIC; | |
| 451 } | |
| 452 | |
| 453 /** | |
| 454 * Returns the Application's flags, used to determine if Chrome was installe
d as part of the | |
| 455 * system image. | |
| 456 * @return The Application's flags. | |
| 457 */ | |
| 458 @VisibleForTesting | |
| 459 int getApplicationFlags() { | |
| 460 return getApplicationInfo().flags; | |
| 461 } | |
| 462 | |
| 463 /** | |
| 464 * Reads the data back from the file it was saved to. Uses SharedPreference
s to handle I/O. | 343 * Reads the data back from the file it was saved to. Uses SharedPreference
s to handle I/O. |
| 465 * Sanity checks are performed on the timestamps to guard against clock chan
ging. | 344 * Sanity checks are performed on the timestamps to guard against clock chan
ging. |
| 466 */ | 345 */ |
| 467 @VisibleForTesting | 346 @VisibleForTesting |
| 468 void restoreState(Context context) { | 347 void restoreState(Context context) { |
| 469 if (mStateHasBeenRestored) return; | 348 if (mStateHasBeenRestored) return; |
| 470 long currentTime = getBackoffScheduler().getCurrentTime(); | 349 |
| 350 String installSource = |
| 351 mDelegate.isInSystemImage() ? INSTALL_SOURCE_SYSTEM : INSTALL_SO
URCE_ORGANIC; |
| 352 ExponentialBackoffScheduler scheduler = getBackoffScheduler(); |
| 353 long currentTime = scheduler.getCurrentTime(); |
| 471 | 354 |
| 472 SharedPreferences preferences = OmahaBase.getSharedPreferences(context); | 355 SharedPreferences preferences = OmahaBase.getSharedPreferences(context); |
| 473 mTimestampForNewRequest = | 356 mTimestampForNewRequest = |
| 474 preferences.getLong(OmahaBase.PREF_TIMESTAMP_FOR_NEW_REQUEST, cu
rrentTime); | 357 preferences.getLong(OmahaBase.PREF_TIMESTAMP_FOR_NEW_REQUEST, cu
rrentTime); |
| 475 mTimestampForNextPostAttempt = | 358 mTimestampForNextPostAttempt = |
| 476 preferences.getLong(OmahaBase.PREF_TIMESTAMP_FOR_NEXT_POST_ATTEM
PT, currentTime); | 359 preferences.getLong(OmahaBase.PREF_TIMESTAMP_FOR_NEXT_POST_ATTEM
PT, currentTime); |
| 477 mTimestampOfInstall = preferences.getLong(OmahaBase.PREF_TIMESTAMP_OF_IN
STALL, currentTime); | 360 mTimestampOfInstall = preferences.getLong(OmahaBase.PREF_TIMESTAMP_OF_IN
STALL, currentTime); |
| 478 mSendInstallEvent = preferences.getBoolean(OmahaBase.PREF_SEND_INSTALL_E
VENT, true); | 361 mSendInstallEvent = preferences.getBoolean(OmahaBase.PREF_SEND_INSTALL_E
VENT, true); |
| 479 mInstallSource = | 362 mInstallSource = preferences.getString(OmahaBase.PREF_INSTALL_SOURCE, in
stallSource); |
| 480 preferences.getString(OmahaBase.PREF_INSTALL_SOURCE, determineIn
stallSource()); | |
| 481 mLatestVersion = preferences.getString(OmahaBase.PREF_LATEST_VERSION, ""
); | 363 mLatestVersion = preferences.getString(OmahaBase.PREF_LATEST_VERSION, ""
); |
| 482 mMarketURL = preferences.getString(OmahaBase.PREF_MARKET_URL, ""); | 364 mMarketURL = preferences.getString(OmahaBase.PREF_MARKET_URL, ""); |
| 483 | 365 |
| 484 // If we're not sending an install event, don't bother restoring the req
uest ID: | 366 // If we're not sending an install event, don't bother restoring the req
uest ID: |
| 485 // the server does not expect to have persisted request IDs for pings or
update checks. | 367 // the server does not expect to have persisted request IDs for pings or
update checks. |
| 486 String persistedRequestId = mSendInstallEvent | 368 String persistedRequestId = mSendInstallEvent |
| 487 ? preferences.getString(OmahaBase.PREF_PERSISTED_REQUEST_ID, INV
ALID_REQUEST_ID) | 369 ? preferences.getString(OmahaBase.PREF_PERSISTED_REQUEST_ID, INV
ALID_REQUEST_ID) |
| 488 : INVALID_REQUEST_ID; | 370 : INVALID_REQUEST_ID; |
| 489 long requestTimestamp = | 371 long requestTimestamp = |
| 490 preferences.getLong(OmahaBase.PREF_TIMESTAMP_OF_REQUEST, INVALID
_TIMESTAMP); | 372 preferences.getLong(OmahaBase.PREF_TIMESTAMP_OF_REQUEST, INVALID
_TIMESTAMP); |
| 491 mCurrentRequest = requestTimestamp == INVALID_TIMESTAMP | 373 mCurrentRequest = requestTimestamp == INVALID_TIMESTAMP |
| 492 ? null : createRequestData(requestTimestamp, persistedRequestId)
; | 374 ? null : createRequestData(requestTimestamp, persistedRequestId)
; |
| 493 | 375 |
| 494 // Confirm that the timestamp for the next request is less than the base
delay. | 376 // Confirm that the timestamp for the next request is less than the base
delay. |
| 495 long delayToNewRequest = mTimestampForNewRequest - currentTime; | 377 long delayToNewRequest = mTimestampForNewRequest - currentTime; |
| 496 if (delayToNewRequest > MS_BETWEEN_REQUESTS) { | 378 if (delayToNewRequest > MS_BETWEEN_REQUESTS) { |
| 497 Log.w(TAG, "Delay to next request (" + delayToNewRequest | 379 Log.w(TAG, "Delay to next request (" + delayToNewRequest |
| 498 + ") is longer than expected. Resetting to now."); | 380 + ") is longer than expected. Resetting to now."); |
| 499 mTimestampForNewRequest = currentTime; | 381 mTimestampForNewRequest = currentTime; |
| 500 } | 382 } |
| 501 | 383 |
| 502 // Confirm that the timestamp for the next POST is less than the current
delay. | 384 // Confirm that the timestamp for the next POST is less than the current
delay. |
| 503 long delayToNextPost = mTimestampForNextPostAttempt - currentTime; | 385 long delayToNextPost = mTimestampForNextPostAttempt - currentTime; |
| 504 if (delayToNextPost > getBackoffScheduler().getGeneratedDelay()) { | 386 long lastGeneratedDelay = scheduler.getGeneratedDelay(); |
| 387 if (delayToNextPost > lastGeneratedDelay) { |
| 505 Log.w(TAG, "Delay to next post attempt (" + delayToNextPost | 388 Log.w(TAG, "Delay to next post attempt (" + delayToNextPost |
| 506 + ") is greater than expected (" + getBackoffScheduler().get
GeneratedDelay() | 389 + ") is greater than expected (" + lastGeneratedDelay |
| 507 + "). Resetting to now."); | 390 + "). Resetting to now."); |
| 508 mTimestampForNextPostAttempt = currentTime; | 391 mTimestampForNextPostAttempt = currentTime; |
| 509 } | 392 } |
| 510 | 393 |
| 394 migrateToNewerChromeVersions(); |
| 511 mStateHasBeenRestored = true; | 395 mStateHasBeenRestored = true; |
| 512 } | 396 } |
| 513 | 397 |
| 514 /** | 398 /** |
| 515 * Writes out the current state to a file. | 399 * Writes out the current state to a file. |
| 516 */ | 400 */ |
| 517 private void saveState(Context context) { | 401 private void saveState(Context context) { |
| 518 SharedPreferences prefs = OmahaBase.getSharedPreferences(context); | 402 SharedPreferences prefs = OmahaBase.getSharedPreferences(context); |
| 519 SharedPreferences.Editor editor = prefs.edit(); | 403 SharedPreferences.Editor editor = prefs.edit(); |
| 520 editor.putBoolean(OmahaBase.PREF_SEND_INSTALL_EVENT, mSendInstallEvent); | 404 editor.putBoolean(OmahaBase.PREF_SEND_INSTALL_EVENT, mSendInstallEvent); |
| 521 editor.putLong(OmahaBase.PREF_TIMESTAMP_OF_INSTALL, mTimestampOfInstall)
; | 405 editor.putLong(OmahaBase.PREF_TIMESTAMP_OF_INSTALL, mTimestampOfInstall)
; |
| 522 editor.putLong( | 406 editor.putLong( |
| 523 OmahaBase.PREF_TIMESTAMP_FOR_NEXT_POST_ATTEMPT, mTimestampForNex
tPostAttempt); | 407 OmahaBase.PREF_TIMESTAMP_FOR_NEXT_POST_ATTEMPT, mTimestampForNex
tPostAttempt); |
| 524 editor.putLong(OmahaBase.PREF_TIMESTAMP_FOR_NEW_REQUEST, mTimestampForNe
wRequest); | 408 editor.putLong(OmahaBase.PREF_TIMESTAMP_FOR_NEW_REQUEST, mTimestampForNe
wRequest); |
| 525 editor.putLong(OmahaBase.PREF_TIMESTAMP_OF_REQUEST, | 409 editor.putLong(OmahaBase.PREF_TIMESTAMP_OF_REQUEST, |
| 526 hasRequest() ? mCurrentRequest.getCreationTimestamp() : INVALID_
TIMESTAMP); | 410 hasRequest() ? mCurrentRequest.getCreationTimestamp() : INVALID_
TIMESTAMP); |
| 527 editor.putString(OmahaBase.PREF_PERSISTED_REQUEST_ID, | 411 editor.putString(OmahaBase.PREF_PERSISTED_REQUEST_ID, |
| 528 hasRequest() ? mCurrentRequest.getRequestID() : INVALID_REQUEST_
ID); | 412 hasRequest() ? mCurrentRequest.getRequestID() : INVALID_REQUEST_
ID); |
| 529 editor.putString( | 413 editor.putString( |
| 530 OmahaBase.PREF_LATEST_VERSION, mLatestVersion == null ? "" : mLa
testVersion); | 414 OmahaBase.PREF_LATEST_VERSION, mLatestVersion == null ? "" : mLa
testVersion); |
| 531 editor.putString(OmahaBase.PREF_MARKET_URL, mMarketURL == null ? "" : mM
arketURL); | 415 editor.putString(OmahaBase.PREF_MARKET_URL, mMarketURL == null ? "" : mM
arketURL); |
| 532 editor.putString(OmahaBase.PREF_INSTALL_SOURCE, mInstallSource); | 416 editor.putString(OmahaBase.PREF_INSTALL_SOURCE, mInstallSource); |
| 533 editor.apply(); | 417 editor.apply(); |
| 418 |
| 419 mDelegate.onSaveStateDone(mTimestampForNewRequest, mTimestampForNextPost
Attempt); |
| 534 } | 420 } |
| 535 | 421 |
| 536 /** | 422 private void migrateToNewerChromeVersions() { |
| 537 * Generates a random UUID. | 423 // Remove any repeating alarms in favor of the new scheduling setup on M
58 and up. |
| 538 */ | 424 // Seems cheaper to cancel the alarm repeatedly than to store a SharedPr
eference and never |
| 539 @VisibleForTesting | 425 // do it again. |
| 540 protected String generateRandomUUID() { | 426 Intent intent = new Intent(getContext(), OmahaClient.class); |
| 541 return UUID.randomUUID().toString(); | 427 intent.setAction(ACTION_REGISTER_REQUEST); |
| 428 getBackoffScheduler().cancelAlarm(intent); |
| 542 } | 429 } |
| 543 | 430 |
| 544 protected final RequestGenerator getRequestGenerator() { | 431 Context getContext() { |
| 545 if (mGenerator == null) mGenerator = createRequestGenerator(this); | 432 return mDelegate.getContext(); |
| 546 return mGenerator; | |
| 547 } | 433 } |
| 548 | 434 |
| 549 protected final ExponentialBackoffScheduler getBackoffScheduler() { | 435 private RequestGenerator getRequestGenerator() { |
| 550 if (mBackoffScheduler == null) { | 436 return mDelegate.getRequestGenerator(); |
| 551 mBackoffScheduler = createBackoffScheduler( | 437 } |
| 552 OmahaBase.PREF_PACKAGE, this, MS_POST_BASE_DELAY, MS_POST_MA
X_DELAY); | 438 |
| 553 } | 439 private ExponentialBackoffScheduler getBackoffScheduler() { |
| 554 return mBackoffScheduler; | 440 return mDelegate.getScheduler(); |
| 441 } |
| 442 |
| 443 void setDelegateForTests(OmahaDelegate delegate) { |
| 444 mDelegate = delegate; |
| 555 } | 445 } |
| 556 } | 446 } |
| OLD | NEW |