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.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; |
25 import java.util.HashMap; | |
26 import java.util.Map; | |
26 import java.util.concurrent.TimeUnit; | 27 import java.util.concurrent.TimeUnit; |
27 | 28 |
28 /** | 29 /** |
29 * Fetches the variations seed before the actual first run of Chrome. | 30 * Fetches the variations seed before the actual first run of Chrome. |
30 */ | 31 */ |
31 public class VariationsSeedFetcher { | 32 public class VariationsSeedFetcher { |
32 private static final String TAG = "VariationsSeedFetch"; | 33 private static final String TAG = "VariationsSeedFetch"; |
33 private static final String VARIATIONS_SERVER_URL = | 34 |
34 "https://clientservices.googleapis.com/chrome-variations/seed?osname =android"; | 35 public static final String VARIATIONS_PLATFORM = "android"; |
Alexei Svitkine (slow)
2017/07/06 22:01:10
Nit: VARIATIONS_PLATFORM_ANDROID
Also, add a blan
| |
36 public static final String VARIATIONS_SERVER_URL = | |
37 "https://clientservices.googleapis.com/chrome-variations/seed?osname ="; | |
38 | |
39 public static final String SIGNATURE_HEADERFIELD = "X-Seed-Signature"; | |
40 public static final String COUNTRY_HEADERFIELD = "X-Country"; | |
41 public static final String DATE_HEADERFIELD = "Date"; | |
42 public static final String IM_HEADERFIELD = "IM"; | |
35 | 43 |
36 private static final int BUFFER_SIZE = 4096; | 44 private static final int BUFFER_SIZE = 4096; |
37 private static final int READ_TIMEOUT = 3000; // time in ms | 45 private static final int READ_TIMEOUT = 3000; // time in ms |
38 private static final int REQUEST_TIMEOUT = 1000; // time in ms | 46 private static final int REQUEST_TIMEOUT = 1000; // time in ms |
39 | 47 |
40 // Values for the "Variations.FirstRun.SeedFetchResult" sparse histogram, wh ich also logs | 48 // 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. | 49 // 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. | 50 // 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; | 51 private static final int SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION = -3; |
44 private static final int SEED_FETCH_RESULT_TIMEOUT = -2; | 52 private static final int SEED_FETCH_RESULT_TIMEOUT = -2; |
(...skipping 25 matching lines...) Expand all Loading... | |
70 * Override the VariationsSeedFetcher, typically with a mock, for testing cl asses that depend on | 78 * Override the VariationsSeedFetcher, typically with a mock, for testing cl asses that depend on |
71 * this one. | 79 * this one. |
72 * @param fetcher the mock. | 80 * @param fetcher the mock. |
73 */ | 81 */ |
74 @VisibleForTesting | 82 @VisibleForTesting |
75 public static void setVariationsSeedFetcherForTesting(VariationsSeedFetcher fetcher) { | 83 public static void setVariationsSeedFetcherForTesting(VariationsSeedFetcher fetcher) { |
76 sInstance = fetcher; | 84 sInstance = fetcher; |
77 } | 85 } |
78 | 86 |
79 @VisibleForTesting | 87 @VisibleForTesting |
80 protected HttpURLConnection getServerConnection(String restrictMode) | 88 protected HttpURLConnection getServerConnection(String urlString, String res trictMode) |
81 throws MalformedURLException, IOException { | 89 throws MalformedURLException, IOException { |
82 String urlString = VARIATIONS_SERVER_URL; | |
83 if (restrictMode != null && !restrictMode.isEmpty()) { | 90 if (restrictMode != null && !restrictMode.isEmpty()) { |
84 urlString += "&restrict=" + restrictMode; | 91 urlString += "&restrict=" + restrictMode; |
85 } | 92 } |
86 URL url = new URL(urlString); | 93 URL url = new URL(urlString); |
87 return (HttpURLConnection) url.openConnection(); | 94 return (HttpURLConnection) url.openConnection(); |
88 } | 95 } |
89 | 96 |
90 /** | 97 /** |
91 * Fetch the first run variations seed. | 98 * Fetch the first run variations seed. |
92 * @param restrictMode The restrict mode parameter to pass to the server via a URL param. | 99 * @param restrictMode The restrict mode parameter to pass to the server via a URL param. |
93 */ | 100 */ |
94 public void fetchSeed(String restrictMode) { | 101 public void fetchSeed(String restrictMode) { |
95 assert !ThreadUtils.runningOnUiThread(); | 102 assert !ThreadUtils.runningOnUiThread(); |
96 // Prevent multiple simultaneous fetches | 103 // Prevent multiple simultaneous fetches |
97 synchronized (sLock) { | 104 synchronized (sLock) { |
98 Context context = ContextUtils.getApplicationContext(); | |
99 SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); | 105 SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); |
100 // Early return if an attempt has already been made to fetch the see d, even if it | 106 // 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 | 107 // 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. | 108 // 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 | 109 // 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. | 110 // Android preference that is set when the seed is fetched by the na tive code. |
105 if (prefs.getBoolean(VARIATIONS_INITIALIZED_PREF, false) | 111 if (prefs.getBoolean(VARIATIONS_INITIALIZED_PREF, false) |
106 || VariationsSeedBridge.hasNativePref()) { | 112 || VariationsSeedBridge.hasNativePref()) { |
107 return; | 113 return; |
108 } | 114 } |
109 downloadContent(context, restrictMode); | 115 |
116 Map<String, String> headerFields = new HashMap<String, String>(); | |
117 byte[] rawSeed = downloadContent( | |
118 VARIATIONS_SERVER_URL + VARIATIONS_PLATFORM, restrictMode, h eaderFields); | |
119 if (rawSeed != null) { | |
120 String signature = headerFields.get(SIGNATURE_HEADERFIELD); | |
121 String country = headerFields.get(COUNTRY_HEADERFIELD); | |
122 String date = headerFields.get(DATE_HEADERFIELD); | |
123 boolean isGzipCompressed = headerFields.get(IM_HEADERFIELD).equa ls("gzip"); | |
124 VariationsSeedBridge.setVariationsFirstRunSeed( | |
125 rawSeed, signature, country, date, isGzipCompressed); | |
126 } | |
110 prefs.edit().putBoolean(VARIATIONS_INITIALIZED_PREF, true).apply(); | 127 prefs.edit().putBoolean(VARIATIONS_INITIALIZED_PREF, true).apply(); |
111 } | 128 } |
112 } | 129 } |
113 | 130 |
114 private void recordFetchResultOrCode(int resultOrCode) { | 131 private void recordFetchResultOrCode(int resultOrCode) { |
115 SparseHistogramSample histogram = | 132 SparseHistogramSample histogram = |
116 new SparseHistogramSample("Variations.FirstRun.SeedFetchResult") ; | 133 new SparseHistogramSample("Variations.FirstRun.SeedFetchResult") ; |
117 histogram.record(resultOrCode); | 134 histogram.record(resultOrCode); |
118 } | 135 } |
119 | 136 |
120 private void recordSeedFetchTime(long timeDeltaMillis) { | 137 private void recordSeedFetchTime(long timeDeltaMillis) { |
121 Log.i(TAG, "Fetched first run seed in " + timeDeltaMillis + " ms"); | 138 Log.i(TAG, "Fetched first run seed in " + timeDeltaMillis + " ms"); |
122 TimesHistogramSample histogram = new TimesHistogramSample( | 139 TimesHistogramSample histogram = new TimesHistogramSample( |
123 "Variations.FirstRun.SeedFetchTime", TimeUnit.MILLISECONDS); | 140 "Variations.FirstRun.SeedFetchTime", TimeUnit.MILLISECONDS); |
124 histogram.record(timeDeltaMillis); | 141 histogram.record(timeDeltaMillis); |
125 } | 142 } |
126 | 143 |
127 private void recordSeedConnectTime(long timeDeltaMillis) { | 144 private void recordSeedConnectTime(long timeDeltaMillis) { |
128 TimesHistogramSample histogram = new TimesHistogramSample( | 145 TimesHistogramSample histogram = new TimesHistogramSample( |
129 "Variations.FirstRun.SeedConnectTime", TimeUnit.MILLISECONDS); | 146 "Variations.FirstRun.SeedConnectTime", TimeUnit.MILLISECONDS); |
130 histogram.record(timeDeltaMillis); | 147 histogram.record(timeDeltaMillis); |
131 } | 148 } |
132 | 149 |
133 private void downloadContent(Context context, String restrictMode) { | 150 public byte[] downloadContent( |
151 String urlString, String restrictMode, Map<String, String> headField s) { | |
134 HttpURLConnection connection = null; | 152 HttpURLConnection connection = null; |
153 byte[] rawSeed = null; | |
135 try { | 154 try { |
136 long startTimeMillis = SystemClock.elapsedRealtime(); | 155 long startTimeMillis = SystemClock.elapsedRealtime(); |
137 connection = getServerConnection(restrictMode); | 156 connection = getServerConnection(urlString, restrictMode); |
138 connection.setReadTimeout(READ_TIMEOUT); | 157 connection.setReadTimeout(READ_TIMEOUT); |
139 connection.setConnectTimeout(REQUEST_TIMEOUT); | 158 connection.setConnectTimeout(REQUEST_TIMEOUT); |
140 connection.setDoInput(true); | 159 connection.setDoInput(true); |
141 connection.setRequestProperty("A-IM", "gzip"); | 160 connection.setRequestProperty("A-IM", "gzip"); |
142 connection.connect(); | 161 connection.connect(); |
143 int responseCode = connection.getResponseCode(); | 162 int responseCode = connection.getResponseCode(); |
144 recordFetchResultOrCode(responseCode); | 163 recordFetchResultOrCode(responseCode); |
145 if (responseCode != HttpURLConnection.HTTP_OK) { | 164 if (responseCode != HttpURLConnection.HTTP_OK) { |
146 Log.w(TAG, "Non-OK response code = %d", responseCode); | 165 Log.w(TAG, "Non-OK response code = %d", responseCode); |
147 return; | 166 return null; |
148 } | 167 } |
149 | 168 |
150 recordSeedConnectTime(SystemClock.elapsedRealtime() - startTimeMilli s); | 169 recordSeedConnectTime(SystemClock.elapsedRealtime() - startTimeMilli s); |
151 // Convert the InputStream into a byte array. | 170 // Convert the InputStream into a byte array. |
152 byte[] rawSeed = getRawSeed(connection); | 171 rawSeed = getRawSeed(connection); |
153 String signature = getHeaderFieldOrEmpty(connection, "X-Seed-Signatu re"); | 172 headFields.put(SIGNATURE_HEADERFIELD, |
154 String country = getHeaderFieldOrEmpty(connection, "X-Country"); | 173 getHeaderFieldOrEmpty(connection, SIGNATURE_HEADERFIELD)); |
155 String date = getHeaderFieldOrEmpty(connection, "Date"); | 174 headFields.put( |
156 boolean isGzipCompressed = getHeaderFieldOrEmpty(connection, "IM").e quals("gzip"); | 175 COUNTRY_HEADERFIELD, getHeaderFieldOrEmpty(connection, COUNT RY_HEADERFIELD)); |
157 VariationsSeedBridge.setVariationsFirstRunSeed( | 176 headFields.put(DATE_HEADERFIELD, getHeaderFieldOrEmpty(connection, D ATE_HEADERFIELD)); |
158 rawSeed, signature, country, date, isGzipCompressed); | 177 headFields.put(IM_HEADERFIELD, getHeaderFieldOrEmpty(connection, IM_ HEADERFIELD)); |
Alexei Svitkine (slow)
2017/07/06 22:01:10
Suggest defining a simple class with these fields
| |
159 recordSeedFetchTime(SystemClock.elapsedRealtime() - startTimeMillis) ; | 178 recordSeedFetchTime(SystemClock.elapsedRealtime() - startTimeMillis) ; |
160 } catch (SocketTimeoutException e) { | 179 } catch (SocketTimeoutException e) { |
161 recordFetchResultOrCode(SEED_FETCH_RESULT_TIMEOUT); | 180 recordFetchResultOrCode(SEED_FETCH_RESULT_TIMEOUT); |
162 Log.w(TAG, "SocketTimeoutException fetching first run seed: ", e); | 181 Log.w(TAG, "SocketTimeoutException fetching first run seed: ", e); |
163 } catch (UnknownHostException e) { | 182 } catch (UnknownHostException e) { |
164 recordFetchResultOrCode(SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION); | 183 recordFetchResultOrCode(SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION); |
165 Log.w(TAG, "UnknownHostException fetching first run seed: ", e); | 184 Log.w(TAG, "UnknownHostException fetching first run seed: ", e); |
166 } catch (IOException e) { | 185 } catch (IOException e) { |
167 recordFetchResultOrCode(SEED_FETCH_RESULT_IOEXCEPTION); | 186 recordFetchResultOrCode(SEED_FETCH_RESULT_IOEXCEPTION); |
168 Log.w(TAG, "IOException fetching first run seed: ", e); | 187 Log.w(TAG, "IOException fetching first run seed: ", e); |
169 } finally { | 188 } finally { |
170 if (connection != null) { | 189 if (connection != null) { |
171 connection.disconnect(); | 190 connection.disconnect(); |
172 } | 191 } |
173 } | 192 } |
193 return rawSeed; | |
paulmiller
2017/07/06 23:09:05
It looks like this can be null, but AwVariationsSe
| |
174 } | 194 } |
175 | 195 |
176 private String getHeaderFieldOrEmpty(HttpURLConnection connection, String na me) { | 196 private String getHeaderFieldOrEmpty(HttpURLConnection connection, String na me) { |
177 String headerField = connection.getHeaderField(name); | 197 String headerField = connection.getHeaderField(name); |
178 if (headerField == null) { | 198 if (headerField == null) { |
179 return ""; | 199 return ""; |
180 } | 200 } |
181 return headerField.trim(); | 201 return headerField.trim(); |
182 } | 202 } |
183 | 203 |
(...skipping 12 matching lines...) Expand all Loading... | |
196 private byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException { | 216 private byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException { |
197 ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); | 217 ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); |
198 byte[] buffer = new byte[BUFFER_SIZE]; | 218 byte[] buffer = new byte[BUFFER_SIZE]; |
199 int charactersReadCount = 0; | 219 int charactersReadCount = 0; |
200 while ((charactersReadCount = inputStream.read(buffer)) != -1) { | 220 while ((charactersReadCount = inputStream.read(buffer)) != -1) { |
201 byteBuffer.write(buffer, 0, charactersReadCount); | 221 byteBuffer.write(buffer, 0, charactersReadCount); |
202 } | 222 } |
203 return byteBuffer.toByteArray(); | 223 return byteBuffer.toByteArray(); |
204 } | 224 } |
205 } | 225 } |
OLD | NEW |