Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(163)

Side by Side Diff: components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java

Issue 2975693002: Add AwVariationsSeedFetchService and refactory VariationsSeedFetcher (Closed)
Patch Set: Add MinAndroidSdkLevel to ignore tests below L Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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.components.variations.firstrun; 5 package org.chromium.components.variations.firstrun;
6 6
7 import android.content.Context;
8 import android.content.SharedPreferences; 7 import android.content.SharedPreferences;
9 import android.os.SystemClock; 8 import android.os.SystemClock;
10 9
11 import org.chromium.base.ContextUtils; 10 import org.chromium.base.ContextUtils;
12 import org.chromium.base.Log; 11 import org.chromium.base.Log;
13 import org.chromium.base.ThreadUtils; 12 import org.chromium.base.ThreadUtils;
14 import org.chromium.base.VisibleForTesting; 13 import org.chromium.base.VisibleForTesting;
15 import org.chromium.base.metrics.CachedMetrics.SparseHistogramSample; 14 import org.chromium.base.metrics.CachedMetrics.SparseHistogramSample;
16 import org.chromium.base.metrics.CachedMetrics.TimesHistogramSample; 15 import org.chromium.base.metrics.CachedMetrics.TimesHistogramSample;
17 16
18 import java.io.ByteArrayOutputStream; 17 import java.io.ByteArrayOutputStream;
19 import java.io.IOException; 18 import java.io.IOException;
20 import java.io.InputStream; 19 import java.io.InputStream;
21 import java.net.HttpURLConnection; 20 import java.net.HttpURLConnection;
22 import java.net.MalformedURLException; 21 import java.net.MalformedURLException;
23 import java.net.SocketTimeoutException; 22 import java.net.SocketTimeoutException;
24 import java.net.URL; 23 import java.net.URL;
25 import java.net.UnknownHostException; 24 import java.net.UnknownHostException;
26 import java.util.concurrent.TimeUnit; 25 import java.util.concurrent.TimeUnit;
27 26
28 /** 27 /**
29 * Fetches the variations seed before the actual first run of Chrome. 28 * Fetches the variations seed before the actual first run of Chrome.
30 */ 29 */
31 public class VariationsSeedFetcher { 30 public class VariationsSeedFetcher {
32 private static final String TAG = "VariationsSeedFetch"; 31 private static final String TAG = "VariationsSeedFetch";
32
33 public enum VariationsPlatform { ANDROID, ANDROID_WEBVIEW }
34
33 private static final String VARIATIONS_SERVER_URL = 35 private static final String VARIATIONS_SERVER_URL =
34 "https://clientservices.googleapis.com/chrome-variations/seed?osname =android"; 36 "https://clientservices.googleapis.com/chrome-variations/seed?osname =";
35 37
36 private static final int BUFFER_SIZE = 4096; 38 private static final int BUFFER_SIZE = 4096;
37 private static final int READ_TIMEOUT = 3000; // time in ms 39 private static final int READ_TIMEOUT = 3000; // time in ms
38 private static final int REQUEST_TIMEOUT = 1000; // time in ms 40 private static final int REQUEST_TIMEOUT = 1000; // time in ms
39 41
40 // Values for the "Variations.FirstRun.SeedFetchResult" sparse histogram, wh ich also logs 42 // Values for the "Variations.FirstRun.SeedFetchResult" sparse histogram, wh ich also logs
41 // HTTP result codes. These are negative so that they don't conflict with th e HTTP codes. 43 // HTTP result codes. These are negative so that they don't conflict with th e HTTP codes.
42 // These values should not be renumbered or re-used since they are logged to UMA. 44 // These values should not be renumbered or re-used since they are logged to UMA.
43 private static final int SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION = -3; 45 private static final int SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION = -3;
44 private static final int SEED_FETCH_RESULT_TIMEOUT = -2; 46 private static final int SEED_FETCH_RESULT_TIMEOUT = -2;
45 private static final int SEED_FETCH_RESULT_IOEXCEPTION = -1; 47 private static final int SEED_FETCH_RESULT_IOEXCEPTION = -1;
46 48
47 @VisibleForTesting 49 @VisibleForTesting
48 static final String VARIATIONS_INITIALIZED_PREF = "variations_initialized"; 50 static final String VARIATIONS_INITIALIZED_PREF = "variations_initialized";
49 51
50 // Synchronization lock 52 // Synchronization lock to make singleton thread-safe.
51 private static final Object sLock = new Object(); 53 private static final Object sLock = new Object();
52 54
53 private static VariationsSeedFetcher sInstance; 55 private static VariationsSeedFetcher sInstance;
54 56
55 @VisibleForTesting 57 @VisibleForTesting
56 VariationsSeedFetcher() {} 58 VariationsSeedFetcher() {}
57 59
58 public static VariationsSeedFetcher get() { 60 public static VariationsSeedFetcher get() {
59 // TODO(aberent) Check not running on UI thread. Doing so however makes Robolectric testing 61 // TODO(aberent) Check not running on UI thread. Doing so however makes Robolectric testing
60 // of dependent classes difficult. 62 // of dependent classes difficult.
61 synchronized (sLock) { 63 synchronized (sLock) {
62 if (sInstance == null) { 64 if (sInstance == null) {
63 sInstance = new VariationsSeedFetcher(); 65 sInstance = new VariationsSeedFetcher();
64 } 66 }
65 return sInstance; 67 return sInstance;
66 } 68 }
67 } 69 }
68 70
69 /** 71 /**
70 * Override the VariationsSeedFetcher, typically with a mock, for testing cl asses that depend on 72 * Override the VariationsSeedFetcher, typically with a mock, for testing cl asses that depend on
71 * this one. 73 * this one.
72 * @param fetcher the mock. 74 * @param fetcher the mock.
73 */ 75 */
74 @VisibleForTesting 76 @VisibleForTesting
75 public static void setVariationsSeedFetcherForTesting(VariationsSeedFetcher fetcher) { 77 public static void setVariationsSeedFetcherForTesting(VariationsSeedFetcher fetcher) {
76 sInstance = fetcher; 78 sInstance = fetcher;
77 } 79 }
78 80
79 @VisibleForTesting 81 @VisibleForTesting
80 protected HttpURLConnection getServerConnection(String restrictMode) 82 protected HttpURLConnection getServerConnection(VariationsPlatform platform,
81 throws MalformedURLException, IOException { 83 String restrictMode) throws MalformedURLException, IOException {
82 String urlString = VARIATIONS_SERVER_URL; 84 String urlString = VARIATIONS_SERVER_URL;
85 switch (platform) {
86 case ANDROID:
87 urlString += "android";
88 break;
89 case ANDROID_WEBVIEW:
90 urlString += "android_webview";
91 break;
92 default:
93 assert false;
94 }
83 if (restrictMode != null && !restrictMode.isEmpty()) { 95 if (restrictMode != null && !restrictMode.isEmpty()) {
84 urlString += "&restrict=" + restrictMode; 96 urlString += "&restrict=" + restrictMode;
85 } 97 }
86 URL url = new URL(urlString); 98 URL url = new URL(urlString);
87 return (HttpURLConnection) url.openConnection(); 99 return (HttpURLConnection) url.openConnection();
88 } 100 }
89 101
90 /** 102 /**
103 * Object holding the seed data and related fields retrieved from HTTP heade rs.
104 */
105 public static class SeedInfo {
106 public String signature;
107 public String country;
108 public String date;
109 public boolean isGzipCompressed;
110 public byte[] seedData;
111 }
112
113 /**
91 * Fetch the first run variations seed. 114 * Fetch the first run variations seed.
92 * @param restrictMode The restrict mode parameter to pass to the server via a URL param. 115 * @param restrictMode The restrict mode parameter to pass to the server via a URL param.
93 */ 116 */
94 public void fetchSeed(String restrictMode) { 117 public void fetchSeed(String restrictMode) {
95 assert !ThreadUtils.runningOnUiThread(); 118 assert !ThreadUtils.runningOnUiThread();
96 // Prevent multiple simultaneous fetches 119 // Prevent multiple simultaneous fetches
97 synchronized (sLock) { 120 synchronized (sLock) {
98 Context context = ContextUtils.getApplicationContext();
99 SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); 121 SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
100 // Early return if an attempt has already been made to fetch the see d, even if it 122 // Early return if an attempt has already been made to fetch the see d, even if it
101 // failed. Only attempt to get the initial Java seed once, since a f ailure probably 123 // failed. Only attempt to get the initial Java seed once, since a f ailure probably
102 // indicates a network problem that is unlikely to be resolved by a second attempt. 124 // indicates a network problem that is unlikely to be resolved by a second attempt.
103 // Note that VariationsSeedBridge.hasNativePref() is a pure Java fun ction, reading an 125 // Note that VariationsSeedBridge.hasNativePref() is a pure Java fun ction, reading an
104 // Android preference that is set when the seed is fetched by the na tive code. 126 // Android preference that is set when the seed is fetched by the na tive code.
105 if (prefs.getBoolean(VARIATIONS_INITIALIZED_PREF, false) 127 if (prefs.getBoolean(VARIATIONS_INITIALIZED_PREF, false)
106 || VariationsSeedBridge.hasNativePref()) { 128 || VariationsSeedBridge.hasNativePref()) {
107 return; 129 return;
108 } 130 }
109 downloadContent(context, restrictMode); 131
132 try {
133 SeedInfo info = downloadContent(VariationsPlatform.ANDROID, rest rictMode);
134 VariationsSeedBridge.setVariationsFirstRunSeed(info.seedData, in fo.signature,
135 info.country, info.date, info.isGzipCompressed);
136 } catch (IOException e) {
137 // Exceptions are handled and logged in the downloadContent meth od, so we don't
138 // need any exception handling here. The only reason we need a c atch-statement here
139 // is because those exceptions are re-thrown from downloadConten t to skip the
140 // normal logic flow within that method.
141 }
142 // VARIATIONS_INITIALIZED_PREF should still be set to true when exce ptions occur
110 prefs.edit().putBoolean(VARIATIONS_INITIALIZED_PREF, true).apply(); 143 prefs.edit().putBoolean(VARIATIONS_INITIALIZED_PREF, true).apply();
111 } 144 }
112 } 145 }
113 146
114 private void recordFetchResultOrCode(int resultOrCode) { 147 private void recordFetchResultOrCode(int resultOrCode) {
115 SparseHistogramSample histogram = 148 SparseHistogramSample histogram =
116 new SparseHistogramSample("Variations.FirstRun.SeedFetchResult") ; 149 new SparseHistogramSample("Variations.FirstRun.SeedFetchResult") ;
117 histogram.record(resultOrCode); 150 histogram.record(resultOrCode);
118 } 151 }
119 152
120 private void recordSeedFetchTime(long timeDeltaMillis) { 153 private void recordSeedFetchTime(long timeDeltaMillis) {
121 Log.i(TAG, "Fetched first run seed in " + timeDeltaMillis + " ms"); 154 Log.i(TAG, "Fetched first run seed in " + timeDeltaMillis + " ms");
122 TimesHistogramSample histogram = new TimesHistogramSample( 155 TimesHistogramSample histogram = new TimesHistogramSample(
123 "Variations.FirstRun.SeedFetchTime", TimeUnit.MILLISECONDS); 156 "Variations.FirstRun.SeedFetchTime", TimeUnit.MILLISECONDS);
124 histogram.record(timeDeltaMillis); 157 histogram.record(timeDeltaMillis);
125 } 158 }
126 159
127 private void recordSeedConnectTime(long timeDeltaMillis) { 160 private void recordSeedConnectTime(long timeDeltaMillis) {
128 TimesHistogramSample histogram = new TimesHistogramSample( 161 TimesHistogramSample histogram = new TimesHistogramSample(
129 "Variations.FirstRun.SeedConnectTime", TimeUnit.MILLISECONDS); 162 "Variations.FirstRun.SeedConnectTime", TimeUnit.MILLISECONDS);
130 histogram.record(timeDeltaMillis); 163 histogram.record(timeDeltaMillis);
131 } 164 }
132 165
133 private void downloadContent(Context context, String restrictMode) { 166 /**
167 * Download the variations seed data with platform and retrictMode.
168 * @param platform the platform parameter to let server only return experime nts which can be
169 * run on that platform.
170 * @param restrictMode the restrict mode parameter to pass to the server via a URL param.
171 * @return the object holds the seed data and its related header fields.
172 * @throws SocketTimeoutException when fetching seed connection times out.
173 * @throws UnknownHostException when fetching seed connection has an unknown host.
174 * @throws IOException when response code is not HTTP_OK or transmission fai ls on the open
175 * connection.
176 */
177 public SeedInfo downloadContent(VariationsPlatform platform, String restrict Mode)
178 throws SocketTimeoutException, UnknownHostException, IOException {
134 HttpURLConnection connection = null; 179 HttpURLConnection connection = null;
135 try { 180 try {
136 long startTimeMillis = SystemClock.elapsedRealtime(); 181 long startTimeMillis = SystemClock.elapsedRealtime();
137 connection = getServerConnection(restrictMode); 182 connection = getServerConnection(platform, restrictMode);
138 connection.setReadTimeout(READ_TIMEOUT); 183 connection.setReadTimeout(READ_TIMEOUT);
139 connection.setConnectTimeout(REQUEST_TIMEOUT); 184 connection.setConnectTimeout(REQUEST_TIMEOUT);
140 connection.setDoInput(true); 185 connection.setDoInput(true);
141 connection.setRequestProperty("A-IM", "gzip"); 186 connection.setRequestProperty("A-IM", "gzip");
142 connection.connect(); 187 connection.connect();
143 int responseCode = connection.getResponseCode(); 188 int responseCode = connection.getResponseCode();
144 recordFetchResultOrCode(responseCode); 189 recordFetchResultOrCode(responseCode);
145 if (responseCode != HttpURLConnection.HTTP_OK) { 190 if (responseCode != HttpURLConnection.HTTP_OK) {
146 Log.w(TAG, "Non-OK response code = %d", responseCode); 191 String errorMsg = "Non-OK response code = " + responseCode;
147 return; 192 Log.w(TAG, errorMsg);
193 throw new IOException(errorMsg);
148 } 194 }
149 195
150 recordSeedConnectTime(SystemClock.elapsedRealtime() - startTimeMilli s); 196 recordSeedConnectTime(SystemClock.elapsedRealtime() - startTimeMilli s);
151 // Convert the InputStream into a byte array. 197
152 byte[] rawSeed = getRawSeed(connection); 198 SeedInfo info = new SeedInfo();
153 String signature = getHeaderFieldOrEmpty(connection, "X-Seed-Signatu re"); 199 info.seedData = getRawSeed(connection);
154 String country = getHeaderFieldOrEmpty(connection, "X-Country"); 200 info.signature = getHeaderFieldOrEmpty(connection, "X-Seed-Signature ");
155 String date = getHeaderFieldOrEmpty(connection, "Date"); 201 info.country = getHeaderFieldOrEmpty(connection, "X-Country");
156 boolean isGzipCompressed = getHeaderFieldOrEmpty(connection, "IM").e quals("gzip"); 202 info.date = getHeaderFieldOrEmpty(connection, "Date");
157 VariationsSeedBridge.setVariationsFirstRunSeed( 203 info.isGzipCompressed = getHeaderFieldOrEmpty(connection, "IM").equa ls("gzip");
158 rawSeed, signature, country, date, isGzipCompressed);
159 recordSeedFetchTime(SystemClock.elapsedRealtime() - startTimeMillis) ; 204 recordSeedFetchTime(SystemClock.elapsedRealtime() - startTimeMillis) ;
205 return info;
160 } catch (SocketTimeoutException e) { 206 } catch (SocketTimeoutException e) {
161 recordFetchResultOrCode(SEED_FETCH_RESULT_TIMEOUT); 207 recordFetchResultOrCode(SEED_FETCH_RESULT_TIMEOUT);
162 Log.w(TAG, "SocketTimeoutException fetching first run seed: ", e); 208 Log.w(TAG, "SocketTimeoutException timeout when fetching variations seed.", e);
209 throw e;
163 } catch (UnknownHostException e) { 210 } catch (UnknownHostException e) {
164 recordFetchResultOrCode(SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION); 211 recordFetchResultOrCode(SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION);
165 Log.w(TAG, "UnknownHostException fetching first run seed: ", e); 212 Log.w(TAG, "UnknownHostException unknown host when fetching variatio ns seed.", e);
213 throw e;
166 } catch (IOException e) { 214 } catch (IOException e) {
167 recordFetchResultOrCode(SEED_FETCH_RESULT_IOEXCEPTION); 215 recordFetchResultOrCode(SEED_FETCH_RESULT_IOEXCEPTION);
168 Log.w(TAG, "IOException fetching first run seed: ", e); 216 Log.w(TAG, "IOException when fetching variations seed.", e);
217 throw e;
169 } finally { 218 } finally {
170 if (connection != null) { 219 if (connection != null) {
171 connection.disconnect(); 220 connection.disconnect();
172 } 221 }
173 } 222 }
174 } 223 }
175 224
176 private String getHeaderFieldOrEmpty(HttpURLConnection connection, String na me) { 225 private String getHeaderFieldOrEmpty(HttpURLConnection connection, String na me) {
177 String headerField = connection.getHeaderField(name); 226 String headerField = connection.getHeaderField(name);
178 if (headerField == null) { 227 if (headerField == null) {
179 return ""; 228 return "";
180 } 229 }
181 return headerField.trim(); 230 return headerField.trim();
182 } 231 }
183 232
184 private byte[] getRawSeed(HttpURLConnection connection) throws IOException { 233 private byte[] getRawSeed(HttpURLConnection connection) throws IOException {
185 InputStream inputStream = null; 234 InputStream inputStream = null;
186 try { 235 try {
187 inputStream = connection.getInputStream(); 236 inputStream = connection.getInputStream();
188 return convertInputStreamToByteArray(inputStream); 237 return convertInputStreamToByteArray(inputStream);
189 } finally { 238 } finally {
190 if (inputStream != null) { 239 if (inputStream != null) {
191 inputStream.close(); 240 inputStream.close();
192 } 241 }
193 } 242 }
194 } 243 }
195 244
196 private byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException { 245 public static byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException {
Alexei Svitkine (slow) 2017/07/20 14:36:36 Add JavaDoc if you're making it public.
197 ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); 246 ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
198 byte[] buffer = new byte[BUFFER_SIZE]; 247 byte[] buffer = new byte[BUFFER_SIZE];
199 int charactersReadCount = 0; 248 int charactersReadCount = 0;
200 while ((charactersReadCount = inputStream.read(buffer)) != -1) { 249 while ((charactersReadCount = inputStream.read(buffer)) != -1) {
201 byteBuffer.write(buffer, 0, charactersReadCount); 250 byteBuffer.write(buffer, 0, charactersReadCount);
202 } 251 }
203 return byteBuffer.toByteArray(); 252 return byteBuffer.toByteArray();
204 } 253 }
205 } 254 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698