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

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: Update for comments of Patch 6 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
Alexei Svitkine (slow) 2017/07/12 19:01:21 Nit: .
yiyuny 2017/07/12 22:22:06 Done.
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 in the downloadContent function and re throwing the
138 // exception is to stop the normal logic flow after it, so no er ror-handling here.
139 // Not explicitly handing SocketTimeoutException and UnknownHost Exception for they
140 // are both subclasses of IOException.
141 }
110 prefs.edit().putBoolean(VARIATIONS_INITIALIZED_PREF, true).apply(); 142 prefs.edit().putBoolean(VARIATIONS_INITIALIZED_PREF, true).apply();
Alexei Svitkine (slow) 2017/07/12 19:01:21 Should this still be set if there was an exception
yiyuny 2017/07/12 22:22:06 Done.
111 } 143 }
112 } 144 }
113 145
114 private void recordFetchResultOrCode(int resultOrCode) { 146 private void recordFetchResultOrCode(int resultOrCode) {
115 SparseHistogramSample histogram = 147 SparseHistogramSample histogram =
116 new SparseHistogramSample("Variations.FirstRun.SeedFetchResult") ; 148 new SparseHistogramSample("Variations.FirstRun.SeedFetchResult") ;
117 histogram.record(resultOrCode); 149 histogram.record(resultOrCode);
118 } 150 }
119 151
120 private void recordSeedFetchTime(long timeDeltaMillis) { 152 private void recordSeedFetchTime(long timeDeltaMillis) {
121 Log.i(TAG, "Fetched first run seed in " + timeDeltaMillis + " ms"); 153 Log.i(TAG, "Fetched first run seed in " + timeDeltaMillis + " ms");
122 TimesHistogramSample histogram = new TimesHistogramSample( 154 TimesHistogramSample histogram = new TimesHistogramSample(
123 "Variations.FirstRun.SeedFetchTime", TimeUnit.MILLISECONDS); 155 "Variations.FirstRun.SeedFetchTime", TimeUnit.MILLISECONDS);
124 histogram.record(timeDeltaMillis); 156 histogram.record(timeDeltaMillis);
125 } 157 }
126 158
127 private void recordSeedConnectTime(long timeDeltaMillis) { 159 private void recordSeedConnectTime(long timeDeltaMillis) {
128 TimesHistogramSample histogram = new TimesHistogramSample( 160 TimesHistogramSample histogram = new TimesHistogramSample(
129 "Variations.FirstRun.SeedConnectTime", TimeUnit.MILLISECONDS); 161 "Variations.FirstRun.SeedConnectTime", TimeUnit.MILLISECONDS);
130 histogram.record(timeDeltaMillis); 162 histogram.record(timeDeltaMillis);
131 } 163 }
132 164
133 private void downloadContent(Context context, String restrictMode) { 165 /**
166 * Download the variations seed data with platform and retrictMode.
167 * @param platform The platform parameter to let server only return experime nts which can be
168 * run on that platform.
169 * @param restrictMode The restrict mode parameter to pass to the server via a URL param.
170 * @throws SocketTimeoutException when fetching seed connection times out.
171 * @throws UnknownHostException when fetching seed connection has an unknown host.
172 * @throws IOException when response code is not HTTP_OK or transmission fai ls on the open
173 * connection.
174 */
175 public SeedInfo downloadContent(VariationsPlatform platform, String restrict Mode)
176 throws SocketTimeoutException, UnknownHostException, IOException {
134 HttpURLConnection connection = null; 177 HttpURLConnection connection = null;
178 SeedInfo info = null;
135 try { 179 try {
136 long startTimeMillis = SystemClock.elapsedRealtime(); 180 long startTimeMillis = SystemClock.elapsedRealtime();
137 connection = getServerConnection(restrictMode); 181 connection = getServerConnection(platform, restrictMode);
138 connection.setReadTimeout(READ_TIMEOUT); 182 connection.setReadTimeout(READ_TIMEOUT);
139 connection.setConnectTimeout(REQUEST_TIMEOUT); 183 connection.setConnectTimeout(REQUEST_TIMEOUT);
140 connection.setDoInput(true); 184 connection.setDoInput(true);
141 connection.setRequestProperty("A-IM", "gzip"); 185 connection.setRequestProperty("A-IM", "gzip");
142 connection.connect(); 186 connection.connect();
143 int responseCode = connection.getResponseCode(); 187 int responseCode = connection.getResponseCode();
144 recordFetchResultOrCode(responseCode); 188 recordFetchResultOrCode(responseCode);
145 if (responseCode != HttpURLConnection.HTTP_OK) { 189 if (responseCode != HttpURLConnection.HTTP_OK) {
146 Log.w(TAG, "Non-OK response code = %d", responseCode); 190 String errorMsg = "Non-OK response code = " + responseCode;
147 return; 191 Log.w(TAG, errorMsg);
192 throw new IOException(errorMsg);
148 } 193 }
149 194
150 recordSeedConnectTime(SystemClock.elapsedRealtime() - startTimeMilli s); 195 recordSeedConnectTime(SystemClock.elapsedRealtime() - startTimeMilli s);
151 // Convert the InputStream into a byte array. 196
152 byte[] rawSeed = getRawSeed(connection); 197 info = new SeedInfo();
153 String signature = getHeaderFieldOrEmpty(connection, "X-Seed-Signatu re"); 198 info.seedData = getRawSeed(connection);
154 String country = getHeaderFieldOrEmpty(connection, "X-Country"); 199 info.signature = getHeaderFieldOrEmpty(connection, "X-Seed-Signature ");
155 String date = getHeaderFieldOrEmpty(connection, "Date"); 200 info.country = getHeaderFieldOrEmpty(connection, "X-Country");
156 boolean isGzipCompressed = getHeaderFieldOrEmpty(connection, "IM").e quals("gzip"); 201 info.date = getHeaderFieldOrEmpty(connection, "Date");
157 VariationsSeedBridge.setVariationsFirstRunSeed( 202 info.isGzipCompressed = getHeaderFieldOrEmpty(connection, "IM").equa ls("gzip");
158 rawSeed, signature, country, date, isGzipCompressed);
159 recordSeedFetchTime(SystemClock.elapsedRealtime() - startTimeMillis) ; 203 recordSeedFetchTime(SystemClock.elapsedRealtime() - startTimeMillis) ;
204 return info;
160 } catch (SocketTimeoutException e) { 205 } catch (SocketTimeoutException e) {
161 recordFetchResultOrCode(SEED_FETCH_RESULT_TIMEOUT); 206 recordFetchResultOrCode(SEED_FETCH_RESULT_TIMEOUT);
162 Log.w(TAG, "SocketTimeoutException fetching first run seed: ", e); 207 Log.w(TAG, "SocketTimeoutException timeout when fetching variations seed.", e);
208 throw e;
163 } catch (UnknownHostException e) { 209 } catch (UnknownHostException e) {
164 recordFetchResultOrCode(SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION); 210 recordFetchResultOrCode(SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION);
165 Log.w(TAG, "UnknownHostException fetching first run seed: ", e); 211 Log.w(TAG, "UnknownHostException unknown host when fetching variatio ns seed.", e);
212 throw e;
166 } catch (IOException e) { 213 } catch (IOException e) {
167 recordFetchResultOrCode(SEED_FETCH_RESULT_IOEXCEPTION); 214 recordFetchResultOrCode(SEED_FETCH_RESULT_IOEXCEPTION);
168 Log.w(TAG, "IOException fetching first run seed: ", e); 215 Log.w(TAG,
216 "IOException I/O errors while opening the connection to fetc h variations seed.",
Alexei Svitkine (slow) 2017/07/12 19:01:21 Nit: Does this really need to be so verbose? I thi
yiyuny 2017/07/12 22:22:06 Done.
217 e);
218 throw e;
169 } finally { 219 } finally {
170 if (connection != null) { 220 if (connection != null) {
171 connection.disconnect(); 221 connection.disconnect();
172 } 222 }
173 } 223 }
174 } 224 }
175 225
176 private String getHeaderFieldOrEmpty(HttpURLConnection connection, String na me) { 226 private String getHeaderFieldOrEmpty(HttpURLConnection connection, String na me) {
177 String headerField = connection.getHeaderField(name); 227 String headerField = connection.getHeaderField(name);
178 if (headerField == null) { 228 if (headerField == null) {
(...skipping 17 matching lines...) Expand all
196 private byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException { 246 private byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException {
197 ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); 247 ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
198 byte[] buffer = new byte[BUFFER_SIZE]; 248 byte[] buffer = new byte[BUFFER_SIZE];
199 int charactersReadCount = 0; 249 int charactersReadCount = 0;
200 while ((charactersReadCount = inputStream.read(buffer)) != -1) { 250 while ((charactersReadCount = inputStream.read(buffer)) != -1) {
201 byteBuffer.write(buffer, 0, charactersReadCount); 251 byteBuffer.write(buffer, 0, charactersReadCount);
202 } 252 }
203 return byteBuffer.toByteArray(); 253 return byteBuffer.toByteArray();
204 } 254 }
205 } 255 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698