Chromium Code Reviews| Index: android_webview/java/src/org/chromium/android_webview/AwVariationsSeedFetchService.java |
| diff --git a/android_webview/java/src/org/chromium/android_webview/AwVariationsSeedFetchService.java b/android_webview/java/src/org/chromium/android_webview/AwVariationsSeedFetchService.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..6efbffb641787adc7897937693fb8b7eab4ac71d |
| --- /dev/null |
| +++ b/android_webview/java/src/org/chromium/android_webview/AwVariationsSeedFetchService.java |
| @@ -0,0 +1,205 @@ |
| +// 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 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) { |
| + AwVariationsSeedFetchService.fetchSeed(); |
| + return null; |
| + } |
| + |
| + @Override |
| + protected void onPostExecute(Void success) { |
| + jobFinished(mJobParams, false /* false -> don't reschedule */); |
| + } |
| + } |
| + |
| + private static void fetchSeed() { |
| + 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; |
| + } |
| + } |
| + |
| + public 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."); |
| + } |
| + |
| + @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); |
| + } |
| + } |
| + |
| + @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(); |
| + } |
| + } |
| + } |
| + |
| + @VisibleForTesting |
| + public static SeedPreference getVariationsSeedPreference() |
|
Alexei Svitkine (slow)
2017/07/18 19:09:40
Public functions should have javadoc.
yiyuny
2017/07/18 21:44:58
Done.
|
| + 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(); |
| + } |
| + } |
| + } |
| + |
| + @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE") // ignoring File.delete() 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); |
| + } |
| + } |
| + } |
| +} |