Chromium Code Reviews| 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; |
| 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(final VariationsPlatform pla tform, |
| 81 throws MalformedURLException, IOException { | 83 final 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 * Store the seed and its related header fields | |
| 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[] rawSeed; | |
| 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.rawSeed, inf o.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(); |
| 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 public SeedInfo downloadContent(final VariationsPlatform platform, final Str ing restrictMode) |
|
Alexei Svitkine (slow)
2017/07/11 19:37:24
Please add javadoc documenting params, return valu
yiyuny
2017/07/11 22:43:37
Done.
| |
| 166 throws SocketTimeoutException, UnknownHostException, IOException { | |
| 134 HttpURLConnection connection = null; | 167 HttpURLConnection connection = null; |
| 168 SeedInfo info = new SeedInfo(); | |
|
Alexei Svitkine (slow)
2017/07/11 19:37:24
Can this be constructed right above line 187?
Ide
| |
| 135 try { | 169 try { |
| 136 long startTimeMillis = SystemClock.elapsedRealtime(); | 170 long startTimeMillis = SystemClock.elapsedRealtime(); |
| 137 connection = getServerConnection(restrictMode); | 171 connection = getServerConnection(platform, restrictMode); |
| 138 connection.setReadTimeout(READ_TIMEOUT); | 172 connection.setReadTimeout(READ_TIMEOUT); |
| 139 connection.setConnectTimeout(REQUEST_TIMEOUT); | 173 connection.setConnectTimeout(REQUEST_TIMEOUT); |
| 140 connection.setDoInput(true); | 174 connection.setDoInput(true); |
| 141 connection.setRequestProperty("A-IM", "gzip"); | 175 connection.setRequestProperty("A-IM", "gzip"); |
| 142 connection.connect(); | 176 connection.connect(); |
| 143 int responseCode = connection.getResponseCode(); | 177 int responseCode = connection.getResponseCode(); |
| 144 recordFetchResultOrCode(responseCode); | 178 recordFetchResultOrCode(responseCode); |
| 145 if (responseCode != HttpURLConnection.HTTP_OK) { | 179 if (responseCode != HttpURLConnection.HTTP_OK) { |
| 146 Log.w(TAG, "Non-OK response code = %d", responseCode); | 180 Log.w(TAG, "Non-OK response code = %d", responseCode); |
| 147 return; | 181 throw new IOException( |
| 182 "HTTP error code when fetching variations seed data: " + responseCode); | |
|
Alexei Svitkine (slow)
2017/07/11 19:37:24
Instead of crafting a whole new string, how about
yiyuny
2017/07/11 22:43:37
Done.
| |
| 148 } | 183 } |
| 149 | 184 |
| 150 recordSeedConnectTime(SystemClock.elapsedRealtime() - startTimeMilli s); | 185 recordSeedConnectTime(SystemClock.elapsedRealtime() - startTimeMilli s); |
| 151 // Convert the InputStream into a byte array. | 186 // Convert the InputStream into a byte array. |
| 152 byte[] rawSeed = getRawSeed(connection); | 187 info.rawSeed = getRawSeed(connection); |
| 153 String signature = getHeaderFieldOrEmpty(connection, "X-Seed-Signatu re"); | 188 info.signature = getHeaderFieldOrEmpty(connection, "X-Seed-Signature "); |
| 154 String country = getHeaderFieldOrEmpty(connection, "X-Country"); | 189 info.country = getHeaderFieldOrEmpty(connection, "X-Country"); |
| 155 String date = getHeaderFieldOrEmpty(connection, "Date"); | 190 info.date = getHeaderFieldOrEmpty(connection, "Date"); |
| 156 boolean isGzipCompressed = getHeaderFieldOrEmpty(connection, "IM").e quals("gzip"); | 191 info.isGzipCompressed = getHeaderFieldOrEmpty(connection, "IM").equa ls("gzip"); |
| 157 VariationsSeedBridge.setVariationsFirstRunSeed( | |
| 158 rawSeed, signature, country, date, isGzipCompressed); | |
| 159 recordSeedFetchTime(SystemClock.elapsedRealtime() - startTimeMillis) ; | 192 recordSeedFetchTime(SystemClock.elapsedRealtime() - startTimeMillis) ; |
| 160 } catch (SocketTimeoutException e) { | 193 } catch (SocketTimeoutException e) { |
| 161 recordFetchResultOrCode(SEED_FETCH_RESULT_TIMEOUT); | 194 recordFetchResultOrCode(SEED_FETCH_RESULT_TIMEOUT); |
| 162 Log.w(TAG, "SocketTimeoutException fetching first run seed: ", e); | 195 Log.w(TAG, "SocketTimeoutException timeout when fetching variations seed.", e); |
| 196 throw e; | |
| 163 } catch (UnknownHostException e) { | 197 } catch (UnknownHostException e) { |
| 164 recordFetchResultOrCode(SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION); | 198 recordFetchResultOrCode(SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION); |
| 165 Log.w(TAG, "UnknownHostException fetching first run seed: ", e); | 199 Log.w(TAG, "UnknownHostException unknown host when fetching variatio ns seed.", e); |
| 200 throw e; | |
| 166 } catch (IOException e) { | 201 } catch (IOException e) { |
| 167 recordFetchResultOrCode(SEED_FETCH_RESULT_IOEXCEPTION); | 202 recordFetchResultOrCode(SEED_FETCH_RESULT_IOEXCEPTION); |
| 168 Log.w(TAG, "IOException fetching first run seed: ", e); | 203 Log.w(TAG, |
| 204 "IOException I/O errors while opening the connection to fetc h variations seed.", | |
| 205 e); | |
| 206 throw e; | |
| 169 } finally { | 207 } finally { |
| 170 if (connection != null) { | 208 if (connection != null) { |
| 171 connection.disconnect(); | 209 connection.disconnect(); |
| 172 } | 210 } |
| 173 } | 211 } |
| 212 return info; | |
| 174 } | 213 } |
| 175 | 214 |
| 176 private String getHeaderFieldOrEmpty(HttpURLConnection connection, String na me) { | 215 private String getHeaderFieldOrEmpty(HttpURLConnection connection, String na me) { |
| 177 String headerField = connection.getHeaderField(name); | 216 String headerField = connection.getHeaderField(name); |
| 178 if (headerField == null) { | 217 if (headerField == null) { |
| 179 return ""; | 218 return ""; |
| 180 } | 219 } |
| 181 return headerField.trim(); | 220 return headerField.trim(); |
| 182 } | 221 } |
| 183 | 222 |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 196 private byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException { | 235 private byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException { |
| 197 ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); | 236 ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); |
| 198 byte[] buffer = new byte[BUFFER_SIZE]; | 237 byte[] buffer = new byte[BUFFER_SIZE]; |
| 199 int charactersReadCount = 0; | 238 int charactersReadCount = 0; |
| 200 while ((charactersReadCount = inputStream.read(buffer)) != -1) { | 239 while ((charactersReadCount = inputStream.read(buffer)) != -1) { |
| 201 byteBuffer.write(buffer, 0, charactersReadCount); | 240 byteBuffer.write(buffer, 0, charactersReadCount); |
| 202 } | 241 } |
| 203 return byteBuffer.toByteArray(); | 242 return byteBuffer.toByteArray(); |
| 204 } | 243 } |
| 205 } | 244 } |
| OLD | NEW |