| Index: components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
|
| diff --git a/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedService.java b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
|
| similarity index 64%
|
| rename from components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedService.java
|
| rename to components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
|
| index 2e5775384e5425e52cf9f083f08e91aa7eac8080..7f117590184ddb894530066a1f8b4447baa7c040 100644
|
| --- a/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedService.java
|
| +++ b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
|
| @@ -4,12 +4,14 @@
|
|
|
| package org.chromium.components.variations.firstrun;
|
|
|
| -import android.app.IntentService;
|
| -import android.content.Intent;
|
| +import android.content.Context;
|
| +import android.content.SharedPreferences;
|
| import android.os.SystemClock;
|
| -import android.support.v4.content.LocalBroadcastManager;
|
|
|
| +import org.chromium.base.ContextUtils;
|
| import org.chromium.base.Log;
|
| +import org.chromium.base.ThreadUtils;
|
| +import org.chromium.base.VisibleForTesting;
|
| import org.chromium.base.metrics.CachedMetrics.SparseHistogramSample;
|
| import org.chromium.base.metrics.CachedMetrics.TimesHistogramSample;
|
|
|
| @@ -17,21 +19,20 @@ import java.io.ByteArrayOutputStream;
|
| import java.io.IOException;
|
| import java.io.InputStream;
|
| import java.net.HttpURLConnection;
|
| +import java.net.MalformedURLException;
|
| import java.net.SocketTimeoutException;
|
| import java.net.URL;
|
| import java.net.UnknownHostException;
|
| import java.util.concurrent.TimeUnit;
|
|
|
| /**
|
| - * Background service that fetches the variations seed before the actual first run of Chrome.
|
| + * Fetches the variations seed before the actual first run of Chrome.
|
| */
|
| -public class VariationsSeedService extends IntentService {
|
| - private static final String TAG = "VariationsSeedServ";
|
| -
|
| - public static final String COMPLETE_BROADCAST = "VariationsseedService.Complete";
|
| -
|
| +public class VariationsSeedFetcher {
|
| + private static final String TAG = "VariationsSeedFetch";
|
| private static final String VARIATIONS_SERVER_URL =
|
| "https://clientservices.googleapis.com/chrome-variations/seed?osname=android";
|
| +
|
| private static final int BUFFER_SIZE = 4096;
|
| private static final int READ_TIMEOUT = 3000; // time in ms
|
| private static final int REQUEST_TIMEOUT = 1000; // time in ms
|
| @@ -43,31 +44,65 @@ public class VariationsSeedService extends IntentService {
|
| private static final int SEED_FETCH_RESULT_TIMEOUT = -2;
|
| private static final int SEED_FETCH_RESULT_IOEXCEPTION = -1;
|
|
|
| - public VariationsSeedService() {
|
| - super(TAG);
|
| - }
|
| + @VisibleForTesting
|
| + static final String VARIATIONS_INITIALIZED_PREF = "variations_initialized";
|
|
|
| - @Override
|
| - public void onHandleIntent(Intent intent) {
|
| - // Early return if the seed has already been fetched. In such a case, either the Java-side
|
| - // variations seed pref is set, or a different Java pref is set that indicates that the
|
| - // seed exists in the native prefs.
|
| - // Note: There is no need to check for a concurrent seed fetch here, because the service
|
| - // runs all its intents on the same worker thread serially.
|
| - if (VariationsSeedBridge.hasJavaPref(getApplicationContext())
|
| - || VariationsSeedBridge.hasNativePref(getApplicationContext())) {
|
| - broadcastCompleteIntent();
|
| - return;
|
| - }
|
| - try {
|
| - downloadContent();
|
| - } finally {
|
| - broadcastCompleteIntent();
|
| + // Synchronization lock
|
| + private static final Object sLock = new Object();
|
| +
|
| + private static VariationsSeedFetcher sInstance;
|
| +
|
| + @VisibleForTesting
|
| + VariationsSeedFetcher() {}
|
| +
|
| + public static VariationsSeedFetcher get() {
|
| + // TODO(aberent) Check not running on UI thread. Doing so however makes Robolectric testing
|
| + // of dependent classes difficult.
|
| + synchronized (sLock) {
|
| + if (sInstance == null) {
|
| + sInstance = new VariationsSeedFetcher();
|
| + }
|
| + return sInstance;
|
| }
|
| }
|
|
|
| - private void broadcastCompleteIntent() {
|
| - LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(COMPLETE_BROADCAST));
|
| + /**
|
| + * Override the VariationsSeedFetcher, typically with a mock, for testing classes that depend on
|
| + * this one.
|
| + * @param fetcher the mock.
|
| + */
|
| + @VisibleForTesting
|
| + public static void setVariationsSeedFetcherForTesting(VariationsSeedFetcher fetcher) {
|
| + sInstance = fetcher;
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + protected HttpURLConnection getServerConnection() throws MalformedURLException, IOException {
|
| + URL url = new URL(VARIATIONS_SERVER_URL);
|
| + return (HttpURLConnection) url.openConnection();
|
| + }
|
| +
|
| + /**
|
| + * Fetch the first run variations seed.
|
| + */
|
| + public void fetchSeed() {
|
| + assert !ThreadUtils.runningOnUiThread();
|
| + // Prevent multiple simultaneous fetches
|
| + synchronized (sLock) {
|
| + Context context = ContextUtils.getApplicationContext();
|
| + SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
|
| + // Early return if an attempt has already been made to fetch the seed, even if it
|
| + // failed. Only attempt to get the initial Java seed once, since a failure probably
|
| + // indicates a network problem that is unlikely to be resolved by a second attempt.
|
| + // Note that VariationsSeedBridge.hasNativePref() is a pure Java function, reading an
|
| + // Android preference that is set when the seed is fetched by the native code.
|
| + if (prefs.getBoolean(VARIATIONS_INITIALIZED_PREF, false)
|
| + || VariationsSeedBridge.hasNativePref(context)) {
|
| + return;
|
| + }
|
| + downloadContent(context);
|
| + prefs.edit().putBoolean(VARIATIONS_INITIALIZED_PREF, true).apply();
|
| + }
|
| }
|
|
|
| private void recordFetchResultOrCode(int resultOrCode) {
|
| @@ -89,12 +124,11 @@ public class VariationsSeedService extends IntentService {
|
| histogram.record(timeDeltaMillis);
|
| }
|
|
|
| - private boolean downloadContent() {
|
| + private void downloadContent(Context context) {
|
| HttpURLConnection connection = null;
|
| try {
|
| long startTimeMillis = SystemClock.elapsedRealtime();
|
| - URL url = new URL(VARIATIONS_SERVER_URL);
|
| - connection = (HttpURLConnection) url.openConnection();
|
| + connection = getServerConnection();
|
| connection.setReadTimeout(READ_TIMEOUT);
|
| connection.setConnectTimeout(REQUEST_TIMEOUT);
|
| connection.setDoInput(true);
|
| @@ -104,7 +138,7 @@ public class VariationsSeedService extends IntentService {
|
| recordFetchResultOrCode(responseCode);
|
| if (responseCode != HttpURLConnection.HTTP_OK) {
|
| Log.w(TAG, "Non-OK response code = %d", responseCode);
|
| - return false;
|
| + return;
|
| }
|
|
|
| recordSeedConnectTime(SystemClock.elapsedRealtime() - startTimeMillis);
|
| @@ -115,21 +149,17 @@ public class VariationsSeedService extends IntentService {
|
| String date = getHeaderFieldOrEmpty(connection, "Date");
|
| boolean isGzipCompressed = getHeaderFieldOrEmpty(connection, "IM").equals("gzip");
|
| VariationsSeedBridge.setVariationsFirstRunSeed(
|
| - getApplicationContext(), rawSeed, signature, country, date, isGzipCompressed);
|
| + context, rawSeed, signature, country, date, isGzipCompressed);
|
| recordSeedFetchTime(SystemClock.elapsedRealtime() - startTimeMillis);
|
| - return true;
|
| } catch (SocketTimeoutException e) {
|
| recordFetchResultOrCode(SEED_FETCH_RESULT_TIMEOUT);
|
| Log.w(TAG, "SocketTimeoutException fetching first run seed: ", e);
|
| - return false;
|
| } catch (UnknownHostException e) {
|
| recordFetchResultOrCode(SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION);
|
| Log.w(TAG, "UnknownHostException fetching first run seed: ", e);
|
| - return false;
|
| } catch (IOException e) {
|
| recordFetchResultOrCode(SEED_FETCH_RESULT_IOEXCEPTION);
|
| Log.w(TAG, "IOException fetching first run seed: ", e);
|
| - return false;
|
| } finally {
|
| if (connection != null) {
|
| connection.disconnect();
|
|
|