 Chromium Code Reviews
 Chromium Code Reviews Issue 2975693002:
  Add AwVariationsSeedFetchService and refactory VariationsSeedFetcher  (Closed)
    
  
    Issue 2975693002:
  Add AwVariationsSeedFetchService and refactory VariationsSeedFetcher  (Closed) 
  | 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); | 
| + } | 
| + } | 
| + } | 
| +} |