| Index: content/public/android/java/src/org/chromium/content/browser/ResourceExtractor.java
|
| diff --git a/content/public/android/java/src/org/chromium/content/browser/ResourceExtractor.java b/content/public/android/java/src/org/chromium/content/browser/ResourceExtractor.java
|
| deleted file mode 100644
|
| index 23aacca28c93749e67b9eb09ac3c5ab44aa938e9..0000000000000000000000000000000000000000
|
| --- a/content/public/android/java/src/org/chromium/content/browser/ResourceExtractor.java
|
| +++ /dev/null
|
| @@ -1,331 +0,0 @@
|
| -// Copyright 2012 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.content.browser;
|
| -
|
| -import android.content.Context;
|
| -import android.content.SharedPreferences;
|
| -import android.content.pm.PackageInfo;
|
| -import android.content.pm.PackageManager;
|
| -import android.content.res.AssetManager;
|
| -import android.os.AsyncTask;
|
| -import android.preference.PreferenceManager;
|
| -import android.util.Log;
|
| -
|
| -import org.chromium.base.PathUtils;
|
| -import org.chromium.ui.base.LocalizationUtils;
|
| -
|
| -import java.io.File;
|
| -import java.io.FileOutputStream;
|
| -import java.io.FilenameFilter;
|
| -import java.io.IOException;
|
| -import java.io.InputStream;
|
| -import java.io.OutputStream;
|
| -import java.util.HashSet;
|
| -import java.util.concurrent.CancellationException;
|
| -import java.util.concurrent.ExecutionException;
|
| -import java.util.regex.Pattern;
|
| -
|
| -/**
|
| - * Handles extracting the necessary resources bundled in an APK and moving them to a location on
|
| - * the file system accessible from the native code.
|
| - */
|
| -public class ResourceExtractor {
|
| -
|
| - private static final String LOGTAG = "ResourceExtractor";
|
| - private static final String LAST_LANGUAGE = "Last language";
|
| - private static final String PAK_FILENAMES = "Pak filenames";
|
| -
|
| - private static String[] sMandatoryPaks = null;
|
| -
|
| - // By default, we attempt to extract a pak file for the users
|
| - // current device locale. Use setExtractImplicitLocale() to
|
| - // change this behavior.
|
| - private static boolean sExtractImplicitLocalePak = true;
|
| -
|
| - private class ExtractTask extends AsyncTask<Void, Void, Void> {
|
| - private static final int BUFFER_SIZE = 16 * 1024;
|
| -
|
| - public ExtractTask() {
|
| - }
|
| -
|
| - @Override
|
| - protected Void doInBackground(Void... unused) {
|
| - if (!mOutputDir.exists() && !mOutputDir.mkdirs()) {
|
| - Log.e(LOGTAG, "Unable to create pak resources directory!");
|
| - return null;
|
| - }
|
| -
|
| - String timestampFile = checkPakTimestamp();
|
| - if (timestampFile != null) {
|
| - deleteFiles(mContext);
|
| - }
|
| -
|
| - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
|
| - HashSet<String> filenames = (HashSet<String>) prefs.getStringSet(
|
| - PAK_FILENAMES, new HashSet<String>());
|
| - String currentLocale = LocalizationUtils.getDefaultLocale();
|
| - String currentLanguage = currentLocale.split("-", 2)[0];
|
| -
|
| - if (prefs.getString(LAST_LANGUAGE, "").equals(currentLanguage)
|
| - && filenames.size() >= sMandatoryPaks.length) {
|
| - boolean filesPresent = true;
|
| - for (String file : filenames) {
|
| - if (!new File(mOutputDir, file).exists()) {
|
| - filesPresent = false;
|
| - break;
|
| - }
|
| - }
|
| - if (filesPresent) return null;
|
| - } else {
|
| - prefs.edit().putString(LAST_LANGUAGE, currentLanguage).apply();
|
| - }
|
| -
|
| - StringBuilder p = new StringBuilder();
|
| - for (String mandatoryPak : sMandatoryPaks) {
|
| - if (p.length() > 0) p.append('|');
|
| - p.append("\\Q" + mandatoryPak + "\\E");
|
| - }
|
| -
|
| - if (sExtractImplicitLocalePak) {
|
| - if (p.length() > 0) p.append('|');
|
| - // As well as the minimum required set of .paks above, we'll also add all .paks that
|
| - // we have for the user's currently selected language.
|
| -
|
| - p.append(currentLanguage);
|
| - p.append("(-\\w+)?\\.pak");
|
| - }
|
| - Pattern paksToInstall = Pattern.compile(p.toString());
|
| -
|
| - AssetManager manager = mContext.getResources().getAssets();
|
| - try {
|
| - // Loop through every asset file that we have in the APK, and look for the
|
| - // ones that we need to extract by trying to match the Patterns that we
|
| - // created above.
|
| - byte[] buffer = null;
|
| - String[] files = manager.list("");
|
| - for (String file : files) {
|
| - if (!paksToInstall.matcher(file).matches()) {
|
| - continue;
|
| - }
|
| - File output = new File(mOutputDir, file);
|
| - if (output.exists()) {
|
| - continue;
|
| - }
|
| -
|
| - InputStream is = null;
|
| - OutputStream os = null;
|
| - try {
|
| - is = manager.open(file);
|
| - os = new FileOutputStream(output);
|
| - Log.i(LOGTAG, "Extracting resource " + file);
|
| - if (buffer == null) {
|
| - buffer = new byte[BUFFER_SIZE];
|
| - }
|
| -
|
| - int count = 0;
|
| - while ((count = is.read(buffer, 0, BUFFER_SIZE)) != -1) {
|
| - os.write(buffer, 0, count);
|
| - }
|
| - os.flush();
|
| -
|
| - // Ensure something reasonable was written.
|
| - if (output.length() == 0) {
|
| - throw new IOException(file + " extracted with 0 length!");
|
| - }
|
| -
|
| - filenames.add(file);
|
| - } finally {
|
| - try {
|
| - if (is != null) {
|
| - is.close();
|
| - }
|
| - } finally {
|
| - if (os != null) {
|
| - os.close();
|
| - }
|
| - }
|
| - }
|
| - }
|
| - } catch (IOException e) {
|
| - // TODO(benm): See crbug/152413.
|
| - // Try to recover here, can we try again after deleting files instead of
|
| - // returning null? It might be useful to gather UMA here too to track if
|
| - // this happens with regularity.
|
| - Log.w(LOGTAG, "Exception unpacking required pak resources: " + e.getMessage());
|
| - deleteFiles(mContext);
|
| - return null;
|
| - }
|
| -
|
| - // Finished, write out a timestamp file if we need to.
|
| -
|
| - if (timestampFile != null) {
|
| - try {
|
| - new File(mOutputDir, timestampFile).createNewFile();
|
| - } catch (IOException e) {
|
| - // Worst case we don't write a timestamp, so we'll re-extract the resource
|
| - // paks next start up.
|
| - Log.w(LOGTAG, "Failed to write resource pak timestamp!");
|
| - }
|
| - }
|
| - // TODO(yusufo): Figure out why remove is required here.
|
| - prefs.edit().remove(PAK_FILENAMES).apply();
|
| - prefs.edit().putStringSet(PAK_FILENAMES, filenames).apply();
|
| - return null;
|
| - }
|
| -
|
| - // Looks for a timestamp file on disk that indicates the version of the APK that
|
| - // the resource paks were extracted from. Returns null if a timestamp was found
|
| - // and it indicates that the resources match the current APK. Otherwise returns
|
| - // a String that represents the filename of a timestamp to create.
|
| - // Note that we do this to avoid adding a BroadcastReceiver on
|
| - // android.content.Intent#ACTION_PACKAGE_CHANGED as that causes process churn
|
| - // on (re)installation of *all* APK files.
|
| - private String checkPakTimestamp() {
|
| - final String TIMESTAMP_PREFIX = "pak_timestamp-";
|
| - PackageManager pm = mContext.getPackageManager();
|
| - PackageInfo pi = null;
|
| -
|
| - try {
|
| - pi = pm.getPackageInfo(mContext.getPackageName(), 0);
|
| - } catch (PackageManager.NameNotFoundException e) {
|
| - return TIMESTAMP_PREFIX;
|
| - }
|
| -
|
| - if (pi == null) {
|
| - return TIMESTAMP_PREFIX;
|
| - }
|
| -
|
| - String expectedTimestamp = TIMESTAMP_PREFIX + pi.versionCode + "-" + pi.lastUpdateTime;
|
| -
|
| - String[] timestamps = mOutputDir.list(new FilenameFilter() {
|
| - @Override
|
| - public boolean accept(File dir, String name) {
|
| - return name.startsWith(TIMESTAMP_PREFIX);
|
| - }
|
| - });
|
| -
|
| - if (timestamps.length != 1) {
|
| - // If there's no timestamp, nuke to be safe as we can't tell the age of the files.
|
| - // If there's multiple timestamps, something's gone wrong so nuke.
|
| - return expectedTimestamp;
|
| - }
|
| -
|
| - if (!expectedTimestamp.equals(timestamps[0])) {
|
| - return expectedTimestamp;
|
| - }
|
| -
|
| - // timestamp file is already up-to date.
|
| - return null;
|
| - }
|
| - }
|
| -
|
| - private final Context mContext;
|
| - private ExtractTask mExtractTask;
|
| - private final File mOutputDir;
|
| -
|
| - private static ResourceExtractor sInstance;
|
| -
|
| - public static ResourceExtractor get(Context context) {
|
| - if (sInstance == null) {
|
| - sInstance = new ResourceExtractor(context);
|
| - }
|
| - return sInstance;
|
| - }
|
| -
|
| - /**
|
| - * Specifies the .pak files that should be extracted from the APK's asset resources directory
|
| - * and moved to {@link #getOutputDirFromContext(Context)}.
|
| - * @param mandatoryPaks The list of pak files to be loaded. If no pak files are
|
| - * required, pass a single empty string.
|
| - */
|
| - public static void setMandatoryPaksToExtract(String... mandatoryPaks) {
|
| - assert (sInstance == null || sInstance.mExtractTask == null)
|
| - : "Must be called before startExtractingResources is called";
|
| - sMandatoryPaks = mandatoryPaks;
|
| -
|
| - }
|
| -
|
| - /**
|
| - * By default the ResourceExtractor will attempt to extract a pak resource for the users
|
| - * currently specified locale. This behavior can be changed with this function and is
|
| - * only needed by tests.
|
| - * @param extract False if we should not attempt to extract a pak file for
|
| - * the users currently selected locale and try to extract only the
|
| - * pak files specified in sMandatoryPaks.
|
| - */
|
| - public static void setExtractImplicitLocaleForTesting(boolean extract) {
|
| - assert (sInstance == null || sInstance.mExtractTask == null)
|
| - : "Must be called before startExtractingResources is called";
|
| - sExtractImplicitLocalePak = extract;
|
| - }
|
| -
|
| - private ResourceExtractor(Context context) {
|
| - mContext = context;
|
| - mOutputDir = getOutputDirFromContext(mContext);
|
| - }
|
| -
|
| - public void waitForCompletion() {
|
| - if (shouldSkipPakExtraction()) {
|
| - return;
|
| - }
|
| -
|
| - assert mExtractTask != null;
|
| -
|
| - try {
|
| - mExtractTask.get();
|
| - } catch (CancellationException e) {
|
| - // Don't leave the files in an inconsistent state.
|
| - deleteFiles(mContext);
|
| - } catch (ExecutionException e2) {
|
| - deleteFiles(mContext);
|
| - } catch (InterruptedException e3) {
|
| - deleteFiles(mContext);
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * This will extract the application pak resources in an
|
| - * AsyncTask. Call waitForCompletion() at the point resources
|
| - * are needed to block until the task completes.
|
| - */
|
| - public void startExtractingResources() {
|
| - if (mExtractTask != null) {
|
| - return;
|
| - }
|
| -
|
| - if (shouldSkipPakExtraction()) {
|
| - return;
|
| - }
|
| -
|
| - mExtractTask = new ExtractTask();
|
| - mExtractTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
| - }
|
| -
|
| - public static File getOutputDirFromContext(Context context) {
|
| - return new File(PathUtils.getDataDirectory(context.getApplicationContext()), "paks");
|
| - }
|
| -
|
| - public static void deleteFiles(Context context) {
|
| - File dir = getOutputDirFromContext(context);
|
| - if (dir.exists()) {
|
| - File[] files = dir.listFiles();
|
| - for (File file : files) {
|
| - if (!file.delete()) {
|
| - Log.w(LOGTAG, "Unable to remove existing resource " + file.getName());
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Pak extraction not necessarily required by the embedder; we allow them to skip
|
| - * this process if they call setMandatoryPaksToExtract with a single empty String.
|
| - */
|
| - private static boolean shouldSkipPakExtraction() {
|
| - // Must call setMandatoryPaksToExtract before beginning resource extraction.
|
| - assert sMandatoryPaks != null;
|
| - return sMandatoryPaks.length == 1 && "".equals(sMandatoryPaks[0]);
|
| - }
|
| -}
|
|
|