Commit f7857086 authored by yiyuny's avatar yiyuny Committed by Commit Bot

Add AwVariationsSeedFetchService and refactory VariationsSeedFetcher

The CL add the AwVariationsSeedFetchService which is a JobService to fetch Finch
seed data from the Finch Server. It reuse some of the code from the
VariationsSeedFetcher, so I refactory the class to expose some reusable
function. The download is triggered by AwVariationsConfigurationService which is
a bound Service to managing the Finch seed data system-wide. The fetched seed
data is going to store in the local directory belongs to the Android WebView.
This is just a prototype of the Variations Seed Fetch Service which is one part
of the work of adding Finch to Android WebView, so there will be another CL
related to the AwVariationsConfigurationService.

BUG=733857

Review-Url: https://codereview.chromium.org/2975693002
Cr-Commit-Position: refs/heads/master@{#488719}
parent 79ed387c
...@@ -815,6 +815,7 @@ android_library("android_webview_java") { ...@@ -815,6 +815,7 @@ android_library("android_webview_java") {
"java/src/org/chromium/android_webview/AwTokenBindingManager.java", "java/src/org/chromium/android_webview/AwTokenBindingManager.java",
"java/src/org/chromium/android_webview/AwViewMethods.java", "java/src/org/chromium/android_webview/AwViewMethods.java",
"java/src/org/chromium/android_webview/AwViewAndroidDelegate.java", "java/src/org/chromium/android_webview/AwViewAndroidDelegate.java",
"java/src/org/chromium/android_webview/AwVariationsSeedFetchService.java",
"java/src/org/chromium/android_webview/AwWebContentsDelegate.java", "java/src/org/chromium/android_webview/AwWebContentsDelegate.java",
"java/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java", "java/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java",
"java/src/org/chromium/android_webview/AwWebContentsObserver.java", "java/src/org/chromium/android_webview/AwWebContentsObserver.java",
...@@ -855,6 +856,7 @@ android_library("android_webview_java") { ...@@ -855,6 +856,7 @@ android_library("android_webview_java") {
"//components/navigation_interception/android:navigation_interception_java", "//components/navigation_interception/android:navigation_interception_java",
"//components/policy/android:policy_java", "//components/policy/android:policy_java",
"//components/safe_browsing_db/android:safe_browsing_java", "//components/safe_browsing_db/android:safe_browsing_java",
"//components/variations/android:variations_java",
"//components/web_contents_delegate_android:web_contents_delegate_android_java", "//components/web_contents_delegate_android:web_contents_delegate_android_java",
"//components/web_restrictions:client_java", "//components/web_restrictions:client_java",
"//content/public/android:content_java", "//content/public/android:content_java",
......
...@@ -39,6 +39,10 @@ ...@@ -39,6 +39,10 @@
android:exported="true" android:exported="true"
android:authorities="{{ manifest_package }}.LicenseContentProvider" /> android:authorities="{{ manifest_package }}.LicenseContentProvider" />
{% if donor_package is not defined %} {% if donor_package is not defined %}
<service android:name="org.chromium.android_webview.AwVariationsSeedFetchService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"
android:process=":variations_service"/>
<service android:name="org.chromium.android_webview.crash.CrashReceiverService" <service android:name="org.chromium.android_webview.crash.CrashReceiverService"
android:exported="true" android:exported="true"
android:process=":crash_receiver_service"/> android:process=":crash_receiver_service"/>
......
...@@ -7,6 +7,7 @@ include_rules = [ ...@@ -7,6 +7,7 @@ include_rules = [
"+components/policy/android/java", "+components/policy/android/java",
"+components/safe_browsing_db/android/java", "+components/safe_browsing_db/android/java",
"+components/web_contents_delegate_android/android/java", "+components/web_contents_delegate_android/android/java",
"+components/variations/android/java",
"+device/gamepad/android/java", "+device/gamepad/android/java",
"+media/base/android/java", "+media/base/android/java",
] ]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.android_webview;
import android.annotation.TargetApi;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.os.AsyncTask;
import android.os.Build;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.SuppressFBWarnings;
import org.chromium.components.variations.firstrun.VariationsSeedFetcher;
import org.chromium.components.variations.firstrun.VariationsSeedFetcher.SeedInfo;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* AwVariationsSeedFetchService is a Job Service to fetch test seed data which is used by Finch
* to enable AB testing experiments in the native code. The fetched data is stored in the local
* directory which belongs to the Service process. This is a prototype of the Variations Seed Fetch
* Service which is one part of the work of adding Finch to Android WebView.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP) // JobService requires API level 21.
public class AwVariationsSeedFetchService extends JobService {
private static final String TAG = "AwVartnsSeedFetchSvc";
public static final String WEBVIEW_VARIATIONS_DIR = "WebView_Variations/";
public static final String SEED_DATA_FILENAME = "variations_seed_data";
public static final String SEED_PREF_FILENAME = "variations_seed_pref";
// Synchronization lock to prevent simultaneous local seed file writing.
private static final Lock sLock = new ReentrantLock();
@Override
public boolean onStartJob(JobParameters params) {
// Ensure we can use ContextUtils later on.
ContextUtils.initApplicationContext(this.getApplicationContext());
new FetchFinchSeedDataTask(params).execute();
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
// This method is called by the JobScheduler to stop a job before it has finished.
// Return true here to reschedule the job.
return true;
}
private class FetchFinchSeedDataTask extends AsyncTask<Void, Void, Void> {
private JobParameters mJobParams;
FetchFinchSeedDataTask(JobParameters params) {
mJobParams = params;
}
@Override
protected Void doInBackground(Void... params) {
fetchVariationsSeed();
return null;
}
@Override
protected void onPostExecute(Void success) {
jobFinished(mJobParams, false /* false -> don't reschedule */);
}
}
private static void fetchVariationsSeed() {
assert !ThreadUtils.runningOnUiThread();
// TryLock will drop calls from other threads when there is a thread executing the function.
// TODO(yiyuny): Add explicitly control to ensure there's only one threading fetching at a
// time and that the seed doesn't get fetched too frequently.
if (sLock.tryLock()) {
try {
SeedInfo seedInfo = VariationsSeedFetcher.get().downloadContent(
VariationsSeedFetcher.VariationsPlatform.ANDROID_WEBVIEW, "");
storeVariationsSeed(seedInfo);
} catch (IOException e) {
// Exceptions are handled and logged in the downloadContent method, so we don't
// need any exception handling here. The only reason we need a catch-statement here
// is because those exceptions are re-thrown from downloadContent to skip the
// normal logic flow within that method.
} finally {
sLock.unlock();
}
}
}
/**
* SeedPreference is used to serialize/deserialize related fields of seed data when reading or
* writing them to the internal storage.
*/
public static class SeedPreference implements Serializable {
/**
* Let the program deserialize the data when the fields are changed.
*/
private static final long serialVersionUID = 0L;
public final String signature;
public final String country;
public final String date;
public final boolean isGzipCompressed;
public SeedPreference(SeedInfo seedInfo) {
signature = seedInfo.signature;
country = seedInfo.country;
date = seedInfo.date;
isGzipCompressed = seedInfo.isGzipCompressed;
}
}
private static File getOrCreateWebViewVariationsDir() throws IOException {
File webViewFileDir = ContextUtils.getApplicationContext().getFilesDir();
File dir = new File(webViewFileDir, WEBVIEW_VARIATIONS_DIR);
if (dir.mkdir() || dir.isDirectory()) {
return dir;
}
throw new IOException("Failed to get or create the WebView variations directory.");
}
/**
* Store the variations seed data and its related header fields into two separate files in the
* directory of Android WebView.
* @param seedInfo The seed data and its related header fields fetched from the finch server.
*/
@VisibleForTesting
public static void storeVariationsSeed(SeedInfo seedInfo) {
FileOutputStream fosSeedData = null;
ObjectOutputStream oosSeedPref = null;
try {
File webViewVariationsDir = getOrCreateWebViewVariationsDir();
File seedDataFile = File.createTempFile(SEED_DATA_FILENAME, null, webViewVariationsDir);
fosSeedData = new FileOutputStream(seedDataFile);
fosSeedData.write(seedInfo.seedData, 0, seedInfo.seedData.length);
renameTempFile(seedDataFile, new File(webViewVariationsDir, SEED_DATA_FILENAME));
// Store separately so that reading large seed data (which is expensive) can be
// prevented when checking the last seed fetch time.
File seedPrefFile = File.createTempFile(SEED_PREF_FILENAME, null, webViewVariationsDir);
oosSeedPref = new ObjectOutputStream(new FileOutputStream(seedPrefFile));
oosSeedPref.writeObject(new SeedPreference(seedInfo));
renameTempFile(seedPrefFile, new File(webViewVariationsDir, SEED_PREF_FILENAME));
} catch (IOException e) {
Log.e(TAG, "Failed to write variations seed data or preference to a file." + e);
} finally {
closeStream(fosSeedData);
closeStream(oosSeedPref);
}
}
/**
* Get the variations seed data from the file in the internal storage.
* @return The byte array which contains the seed data.
* @throws IOException if fail to get or create the WebView Variations directory.
*/
@VisibleForTesting
public static byte[] getVariationsSeedData() throws IOException {
FileInputStream fisSeedData = null;
try {
File webViewVariationsDir = getOrCreateWebViewVariationsDir();
fisSeedData = new FileInputStream(new File(webViewVariationsDir, SEED_DATA_FILENAME));
return VariationsSeedFetcher.convertInputStreamToByteArray(fisSeedData);
} finally {
if (fisSeedData != null) {
fisSeedData.close();
}
}
}
/**
* Get the variations seed preference from the file in the internal storage.
* @return The seed preference which holds related header fields of the seed.
* @throws IOException if fail to get or create the WebView Variations directory.
* @throws ClassNotFoundException if fail to load the class of the serialized object.
*/
@VisibleForTesting
public static SeedPreference getVariationsSeedPreference()
throws IOException, ClassNotFoundException {
ObjectInputStream oisSeedPref = null;
try {
File webViewVariationsDir = getOrCreateWebViewVariationsDir();
oisSeedPref = new ObjectInputStream(
new FileInputStream(new File(webViewVariationsDir, SEED_PREF_FILENAME)));
return (SeedPreference) oisSeedPref.readObject();
} finally {
if (oisSeedPref != null) {
oisSeedPref.close();
}
}
}
/**
* Clear the test data.
* @throws IOException if fail to get or create the WebView Variations directory.
*/
@SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE") // ignoring File.delete() return
@VisibleForTesting
public static void clearDataForTesting() throws IOException {
File webViewVariationsDir = getOrCreateWebViewVariationsDir();
File seedDataFile = new File(webViewVariationsDir, SEED_DATA_FILENAME);
File seedPrefFile = new File(webViewVariationsDir, SEED_PREF_FILENAME);
seedDataFile.delete();
seedPrefFile.delete();
webViewVariationsDir.delete();
}
@SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
// ignoring File.delete() and File.renameTo() return
private static void renameTempFile(File tempFile, File newFile) {
newFile.delete();
tempFile.renameTo(newFile);
}
private static void closeStream(Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close stream. " + e);
}
}
}
}
...@@ -4,6 +4,7 @@ include_rules = [ ...@@ -4,6 +4,7 @@ include_rules = [
"+components/policy/android/java", "+components/policy/android/java",
"+components/policy/android/javatests", "+components/policy/android/javatests",
"+components/safe_browsing_db/android/java", "+components/safe_browsing_db/android/java",
"+components/variations/android/java",
"+content/public/android/java", "+content/public/android/java",
"+content/public/test/android/javatests", "+content/public/test/android/javatests",
"+device/geolocation/android/java", "+device/geolocation/android/java",
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.android_webview.test;
import android.annotation.TargetApi;
import android.os.Build;
import android.support.test.filters.MediumTest;
import android.test.InstrumentationTestCase;
import org.chromium.android_webview.AwVariationsSeedFetchService;
import org.chromium.android_webview.AwVariationsSeedFetchService.SeedPreference;
import org.chromium.base.ContextUtils;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.components.variations.firstrun.VariationsSeedFetcher.SeedInfo;
import java.io.IOException;
import java.util.Arrays;
/**
* Instrumentation tests for AwVariationsSeedFetchService.
*/
@MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP) // Android versions under L don't have JobService.
@TargetApi(Build.VERSION_CODES.LOLLIPOP) // JobService requires API level 21.
public class AwVariationsSeedFetchServiceTest extends InstrumentationTestCase {
protected void setUp() throws Exception {
ContextUtils.initApplicationContextForTests(
getInstrumentation().getTargetContext().getApplicationContext());
}
protected void tearDown() throws Exception {
AwVariationsSeedFetchService.clearDataForTesting();
}
/**
* Ensure reading and writing seed data and pref works correctly.
*/
@MediumTest
public void testReadAndWriteVariationsSeed() throws IOException, ClassNotFoundException {
SeedInfo seedInfo = new SeedInfo();
seedInfo.seedData = "SeedData".getBytes();
seedInfo.signature = "SeedSignature";
seedInfo.country = "SeedCountry";
seedInfo.date = "SeedDate";
seedInfo.isGzipCompressed = true;
AwVariationsSeedFetchService.storeVariationsSeed(seedInfo);
byte[] seedData = AwVariationsSeedFetchService.getVariationsSeedData();
assertTrue(Arrays.equals(seedData, seedInfo.seedData));
SeedPreference seedPreference = AwVariationsSeedFetchService.getVariationsSeedPreference();
assertEquals(seedPreference.signature, seedInfo.signature);
assertEquals(seedPreference.country, seedInfo.country);
assertEquals(seedPreference.date, seedInfo.date);
assertEquals(seedPreference.isGzipCompressed, seedInfo.isGzipCompressed);
}
}
...@@ -118,6 +118,7 @@ instrumentation_test_apk("webview_instrumentation_test_apk") { ...@@ -118,6 +118,7 @@ instrumentation_test_apk("webview_instrumentation_test_apk") {
"//components/policy/android:policy_java", "//components/policy/android:policy_java",
"//components/policy/android:policy_java_test_support", "//components/policy/android:policy_java_test_support",
"//components/safe_browsing_db/android:safe_browsing_java", "//components/safe_browsing_db/android:safe_browsing_java",
"//components/variations/android:variations_java",
"//components/web_contents_delegate_android:web_contents_delegate_android_java", "//components/web_contents_delegate_android:web_contents_delegate_android_java",
"//content/public/android:content_java", "//content/public/android:content_java",
"//content/public/test/android:content_java_test_support", "//content/public/test/android:content_java_test_support",
...@@ -167,6 +168,7 @@ instrumentation_test_apk("webview_instrumentation_test_apk") { ...@@ -167,6 +168,7 @@ instrumentation_test_apk("webview_instrumentation_test_apk") {
"../javatests/src/org/chromium/android_webview/test/AwStrictModeTest.java", "../javatests/src/org/chromium/android_webview/test/AwStrictModeTest.java",
"../javatests/src/org/chromium/android_webview/test/AwTestBase.java", "../javatests/src/org/chromium/android_webview/test/AwTestBase.java",
"../javatests/src/org/chromium/android_webview/test/AwWebContentsObserverTest.java", "../javatests/src/org/chromium/android_webview/test/AwWebContentsObserverTest.java",
"../javatests/src/org/chromium/android_webview/test/AwVariationsSeedFetchServiceTest.java",
"../javatests/src/org/chromium/android_webview/test/AwZoomTest.java", "../javatests/src/org/chromium/android_webview/test/AwZoomTest.java",
"../javatests/src/org/chromium/android_webview/test/CleanupReferenceTest.java", "../javatests/src/org/chromium/android_webview/test/CleanupReferenceTest.java",
"../javatests/src/org/chromium/android_webview/test/ClearHistoryTest.java", "../javatests/src/org/chromium/android_webview/test/ClearHistoryTest.java",
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
package org.chromium.components.variations.firstrun; package org.chromium.components.variations.firstrun;
import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.SystemClock; import android.os.SystemClock;
...@@ -30,8 +29,11 @@ import java.util.concurrent.TimeUnit; ...@@ -30,8 +29,11 @@ import java.util.concurrent.TimeUnit;
*/ */
public class VariationsSeedFetcher { public class VariationsSeedFetcher {
private static final String TAG = "VariationsSeedFetch"; private static final String TAG = "VariationsSeedFetch";
public enum VariationsPlatform { ANDROID, ANDROID_WEBVIEW }
private static final String VARIATIONS_SERVER_URL = private static final String VARIATIONS_SERVER_URL =
"https://clientservices.googleapis.com/chrome-variations/seed?osname=android"; "https://clientservices.googleapis.com/chrome-variations/seed?osname=";
private static final int BUFFER_SIZE = 4096; private static final int BUFFER_SIZE = 4096;
private static final int READ_TIMEOUT = 3000; // time in ms private static final int READ_TIMEOUT = 3000; // time in ms
...@@ -47,7 +49,7 @@ public class VariationsSeedFetcher { ...@@ -47,7 +49,7 @@ public class VariationsSeedFetcher {
@VisibleForTesting @VisibleForTesting
static final String VARIATIONS_INITIALIZED_PREF = "variations_initialized"; static final String VARIATIONS_INITIALIZED_PREF = "variations_initialized";
// Synchronization lock // Synchronization lock to make singleton thread-safe.
private static final Object sLock = new Object(); private static final Object sLock = new Object();
private static VariationsSeedFetcher sInstance; private static VariationsSeedFetcher sInstance;
...@@ -77,9 +79,19 @@ public class VariationsSeedFetcher { ...@@ -77,9 +79,19 @@ public class VariationsSeedFetcher {
} }
@VisibleForTesting @VisibleForTesting
protected HttpURLConnection getServerConnection(String restrictMode) protected HttpURLConnection getServerConnection(VariationsPlatform platform,
throws MalformedURLException, IOException { String restrictMode) throws MalformedURLException, IOException {
String urlString = VARIATIONS_SERVER_URL; String urlString = VARIATIONS_SERVER_URL;
switch (platform) {
case ANDROID:
urlString += "android";
break;
case ANDROID_WEBVIEW:
urlString += "android_webview";
break;
default:
assert false;
}
if (restrictMode != null && !restrictMode.isEmpty()) { if (restrictMode != null && !restrictMode.isEmpty()) {
urlString += "&restrict=" + restrictMode; urlString += "&restrict=" + restrictMode;
} }
...@@ -87,6 +99,17 @@ public class VariationsSeedFetcher { ...@@ -87,6 +99,17 @@ public class VariationsSeedFetcher {
return (HttpURLConnection) url.openConnection(); return (HttpURLConnection) url.openConnection();
} }
/**
* Object holding the seed data and related fields retrieved from HTTP headers.
*/
public static class SeedInfo {
public String signature;
public String country;
public String date;
public boolean isGzipCompressed;
public byte[] seedData;
}
/** /**
* Fetch the first run variations seed. * Fetch the first run variations seed.
* @param restrictMode The restrict mode parameter to pass to the server via a URL param. * @param restrictMode The restrict mode parameter to pass to the server via a URL param.
...@@ -95,7 +118,6 @@ public class VariationsSeedFetcher { ...@@ -95,7 +118,6 @@ public class VariationsSeedFetcher {
assert !ThreadUtils.runningOnUiThread(); assert !ThreadUtils.runningOnUiThread();
// Prevent multiple simultaneous fetches // Prevent multiple simultaneous fetches
synchronized (sLock) { synchronized (sLock) {
Context context = ContextUtils.getApplicationContext();
SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
// Early return if an attempt has already been made to fetch the seed, even if it // 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 // failed. Only attempt to get the initial Java seed once, since a failure probably
...@@ -106,7 +128,18 @@ public class VariationsSeedFetcher { ...@@ -106,7 +128,18 @@ public class VariationsSeedFetcher {
|| VariationsSeedBridge.hasNativePref()) { || VariationsSeedBridge.hasNativePref()) {
return; return;
} }
downloadContent(context, restrictMode);
try {
SeedInfo info = downloadContent(VariationsPlatform.ANDROID, restrictMode);
VariationsSeedBridge.setVariationsFirstRunSeed(info.seedData, info.signature,
info.country, info.date, info.isGzipCompressed);
} catch (IOException e) {
// Exceptions are handled and logged in the downloadContent method, so we don't
// need any exception handling here. The only reason we need a catch-statement here
// is because those exceptions are re-thrown from downloadContent to skip the
// normal logic flow within that method.
}
// VARIATIONS_INITIALIZED_PREF should still be set to true when exceptions occur
prefs.edit().putBoolean(VARIATIONS_INITIALIZED_PREF, true).apply(); prefs.edit().putBoolean(VARIATIONS_INITIALIZED_PREF, true).apply();
} }
} }
...@@ -130,11 +163,23 @@ public class VariationsSeedFetcher { ...@@ -130,11 +163,23 @@ public class VariationsSeedFetcher {
histogram.record(timeDeltaMillis); histogram.record(timeDeltaMillis);
} }
private void downloadContent(Context context, String restrictMode) { /**
* Download the variations seed data with platform and retrictMode.
* @param platform the platform parameter to let server only return experiments which can be
* run on that platform.
* @param restrictMode the restrict mode parameter to pass to the server via a URL param.
* @return the object holds the seed data and its related header fields.
* @throws SocketTimeoutException when fetching seed connection times out.
* @throws UnknownHostException when fetching seed connection has an unknown host.
* @throws IOException when response code is not HTTP_OK or transmission fails on the open
* connection.
*/
public SeedInfo downloadContent(VariationsPlatform platform, String restrictMode)
throws SocketTimeoutException, UnknownHostException, IOException {
HttpURLConnection connection = null; HttpURLConnection connection = null;
try { try {
long startTimeMillis = SystemClock.elapsedRealtime(); long startTimeMillis = SystemClock.elapsedRealtime();
connection = getServerConnection(restrictMode); connection = getServerConnection(platform, restrictMode);
connection.setReadTimeout(READ_TIMEOUT); connection.setReadTimeout(READ_TIMEOUT);
connection.setConnectTimeout(REQUEST_TIMEOUT); connection.setConnectTimeout(REQUEST_TIMEOUT);
connection.setDoInput(true); connection.setDoInput(true);
...@@ -143,29 +188,33 @@ public class VariationsSeedFetcher { ...@@ -143,29 +188,33 @@ public class VariationsSeedFetcher {
int responseCode = connection.getResponseCode(); int responseCode = connection.getResponseCode();
recordFetchResultOrCode(responseCode); recordFetchResultOrCode(responseCode);
if (responseCode != HttpURLConnection.HTTP_OK) { if (responseCode != HttpURLConnection.HTTP_OK) {
Log.w(TAG, "Non-OK response code = %d", responseCode); String errorMsg = "Non-OK response code = " + responseCode;
return; Log.w(TAG, errorMsg);
throw new IOException(errorMsg);
} }
recordSeedConnectTime(SystemClock.elapsedRealtime() - startTimeMillis); recordSeedConnectTime(SystemClock.elapsedRealtime() - startTimeMillis);
// Convert the InputStream into a byte array.
byte[] rawSeed = getRawSeed(connection); SeedInfo info = new SeedInfo();
String signature = getHeaderFieldOrEmpty(connection, "X-Seed-Signature"); info.seedData = getRawSeed(connection);
String country = getHeaderFieldOrEmpty(connection, "X-Country"); info.signature = getHeaderFieldOrEmpty(connection, "X-Seed-Signature");
String date = getHeaderFieldOrEmpty(connection, "Date"); info.country = getHeaderFieldOrEmpty(connection, "X-Country");
boolean isGzipCompressed = getHeaderFieldOrEmpty(connection, "IM").equals("gzip"); info.date = getHeaderFieldOrEmpty(connection, "Date");
VariationsSeedBridge.setVariationsFirstRunSeed( info.isGzipCompressed = getHeaderFieldOrEmpty(connection, "IM").equals("gzip");
rawSeed, signature, country, date, isGzipCompressed);
recordSeedFetchTime(SystemClock.elapsedRealtime() - startTimeMillis); recordSeedFetchTime(SystemClock.elapsedRealtime() - startTimeMillis);
return info;
} catch (SocketTimeoutException e) { } catch (SocketTimeoutException e) {
recordFetchResultOrCode(SEED_FETCH_RESULT_TIMEOUT); recordFetchResultOrCode(SEED_FETCH_RESULT_TIMEOUT);
Log.w(TAG, "SocketTimeoutException fetching first run seed: ", e); Log.w(TAG, "SocketTimeoutException timeout when fetching variations seed.", e);
throw e;
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
recordFetchResultOrCode(SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION); recordFetchResultOrCode(SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION);
Log.w(TAG, "UnknownHostException fetching first run seed: ", e); Log.w(TAG, "UnknownHostException unknown host when fetching variations seed.", e);
throw e;
} catch (IOException e) { } catch (IOException e) {
recordFetchResultOrCode(SEED_FETCH_RESULT_IOEXCEPTION); recordFetchResultOrCode(SEED_FETCH_RESULT_IOEXCEPTION);
Log.w(TAG, "IOException fetching first run seed: ", e); Log.w(TAG, "IOException when fetching variations seed.", e);
throw e;
} finally { } finally {
if (connection != null) { if (connection != null) {
connection.disconnect(); connection.disconnect();
...@@ -173,6 +222,22 @@ public class VariationsSeedFetcher { ...@@ -173,6 +222,22 @@ public class VariationsSeedFetcher {
} }
} }
/**
* Convert a input stream into a byte array.
* @param inputStream the input stream
* @return the byte array which holds the data from the input stream
* @throws IOException if I/O error occurs when reading data from the input stream
*/
public static byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException {
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
byte[] buffer = new byte[BUFFER_SIZE];
int charactersReadCount = 0;
while ((charactersReadCount = inputStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, charactersReadCount);
}
return byteBuffer.toByteArray();
}
private String getHeaderFieldOrEmpty(HttpURLConnection connection, String name) { private String getHeaderFieldOrEmpty(HttpURLConnection connection, String name) {
String headerField = connection.getHeaderField(name); String headerField = connection.getHeaderField(name);
if (headerField == null) { if (headerField == null) {
...@@ -192,14 +257,4 @@ public class VariationsSeedFetcher { ...@@ -192,14 +257,4 @@ public class VariationsSeedFetcher {
} }
} }
} }
private byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException {
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
byte[] buffer = new byte[BUFFER_SIZE];
int charactersReadCount = 0;
while ((charactersReadCount = inputStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, charactersReadCount);
}
return byteBuffer.toByteArray();
}
} }
...@@ -53,7 +53,9 @@ public class VariationsSeedFetcherTest { ...@@ -53,7 +53,9 @@ public class VariationsSeedFetcherTest {
ThreadUtils.setUiThread(mock(Looper.class)); ThreadUtils.setUiThread(mock(Looper.class));
mFetcher = spy(VariationsSeedFetcher.get()); mFetcher = spy(VariationsSeedFetcher.get());
mConnection = mock(HttpURLConnection.class); mConnection = mock(HttpURLConnection.class);
doReturn(mConnection).when(mFetcher).getServerConnection(""); doReturn(mConnection)
.when(mFetcher)
.getServerConnection(VariationsSeedFetcher.VariationsPlatform.ANDROID, "");
mPrefs = ContextUtils.getAppSharedPreferences(); mPrefs = ContextUtils.getAppSharedPreferences();
mPrefs.edit().clear().apply(); mPrefs.edit().clear().apply();
} }
......
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