summaryrefslogtreecommitdiff
path: root/base/android/java/src/org/chromium/base/ResourceExtractor.java
diff options
context:
space:
mode:
Diffstat (limited to 'base/android/java/src/org/chromium/base/ResourceExtractor.java')
-rw-r--r--base/android/java/src/org/chromium/base/ResourceExtractor.java484
1 files changed, 0 insertions, 484 deletions
diff --git a/base/android/java/src/org/chromium/base/ResourceExtractor.java b/base/android/java/src/org/chromium/base/ResourceExtractor.java
deleted file mode 100644
index d44f2fc7c2..0000000000
--- a/base/android/java/src/org/chromium/base/ResourceExtractor.java
+++ /dev/null
@@ -1,484 +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.base;
-
-import android.annotation.TargetApi;
-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.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Trace;
-import android.preference.PreferenceManager;
-import android.util.Log;
-
-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.ArrayList;
-import java.util.List;
-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_LEGACY_NOREUSE = "Pak filenames";
- private static final String ICU_DATA_FILENAME = "icudtl.dat";
- private static final String V8_NATIVES_DATA_FILENAME = "natives_blob.bin";
- private static final String V8_SNAPSHOT_DATA_FILENAME = "snapshot_blob.bin";
-
- 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 static boolean isAppDataFile(String file) {
- return ICU_DATA_FILENAME.equals(file)
- || V8_NATIVES_DATA_FILENAME.equals(file)
- || V8_SNAPSHOT_DATA_FILENAME.equals(file);
- }
-
- private class ExtractTask extends AsyncTask<Void, Void, Void> {
- private static final int BUFFER_SIZE = 16 * 1024;
-
- private final List<Runnable> mCompletionCallbacks = new ArrayList<Runnable>();
-
- public ExtractTask() {
- }
-
- private void doInBackgroundImpl() {
- final File outputDir = getOutputDir();
- final File appDataDir = getAppDataDir();
- if (!outputDir.exists() && !outputDir.mkdirs()) {
- Log.e(LOGTAG, "Unable to create pak resources directory!");
- return;
- }
-
- String timestampFile = null;
- beginTraceSection("checkPakTimeStamp");
- try {
- timestampFile = checkPakTimestamp(outputDir);
- } finally {
- endTraceSection();
- }
- if (timestampFile != null) {
- deleteFiles();
- }
-
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
- String currentLocale = LocaleUtils.getDefaultLocale();
- String currentLanguage = currentLocale.split("-", 2)[0];
- // If everything we need is already there (and the locale hasn't
- // changed), quick exit.
- if (prefs.getString(LAST_LANGUAGE, "").equals(currentLanguage)) {
- boolean filesPresent = true;
- for (String file : sMandatoryPaks) {
- File directory = isAppDataFile(file) ? appDataDir : outputDir;
- if (!new File(directory, file).exists()) {
- filesPresent = false;
- break;
- }
- }
- if (filesPresent) return;
- } 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();
- beginTraceSection("WalkAssets");
- 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(isAppDataFile(file) ? appDataDir : outputDir, file);
- if (output.exists()) {
- continue;
- }
-
- InputStream is = null;
- OutputStream os = null;
- beginTraceSection("ExtractResource");
- 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!");
- }
-
- if (isAppDataFile(file)) {
- // icu and V8 data need to be accessed by a renderer
- // process.
- output.setReadable(true, false);
- }
- } finally {
- try {
- if (is != null) {
- is.close();
- }
- } finally {
- if (os != null) {
- os.close();
- }
- endTraceSection(); // ExtractResource
- }
- }
- }
- } 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();
- return;
- } finally {
- endTraceSection(); // WalkAssets
- }
-
- // Finished, write out a timestamp file if we need to.
- if (timestampFile != null) {
- try {
- new File(outputDir, 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!");
- }
- }
- }
-
- @Override
- protected Void doInBackground(Void... unused) {
- // TODO(lizeb): Use chrome tracing here (and above in
- // doInBackgroundImpl) when it will be possible. This is currently
- // not doable since the native library is not loaded yet, and the
- // TraceEvent calls are dropped before this point.
- beginTraceSection("ResourceExtractor.ExtractTask.doInBackground");
- try {
- doInBackgroundImpl();
- } finally {
- endTraceSection();
- }
- return null;
- }
-
- private void onPostExecuteImpl() {
- for (int i = 0; i < mCompletionCallbacks.size(); i++) {
- mCompletionCallbacks.get(i).run();
- }
- mCompletionCallbacks.clear();
- }
-
- @Override
- protected void onPostExecute(Void result) {
- beginTraceSection("ResourceExtractor.ExtractTask.onPostExecute");
- try {
- onPostExecuteImpl();
- } finally {
- endTraceSection();
- }
- }
-
- // 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(File outputDir) {
- final String timestampPrefix = "pak_timestamp-";
- PackageManager pm = mContext.getPackageManager();
- PackageInfo pi = null;
-
- try {
- pi = pm.getPackageInfo(mContext.getPackageName(), 0);
- } catch (PackageManager.NameNotFoundException e) {
- return timestampPrefix;
- }
-
- if (pi == null) {
- return timestampPrefix;
- }
-
- String expectedTimestamp = timestampPrefix + pi.versionCode + "-" + pi.lastUpdateTime;
-
- String[] timestamps = outputDir.list(new FilenameFilter() {
- @Override
- public boolean accept(File dir, String name) {
- return name.startsWith(timestampPrefix);
- }
- });
-
- 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;
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
- private void beginTraceSection(String section) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return;
- Trace.beginSection(section);
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
- private void endTraceSection() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return;
- Trace.endSection();
- }
- }
-
- private final Context mContext;
- private ExtractTask mExtractTask;
-
- 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.
- */
- @VisibleForTesting
- public static void setExtractImplicitLocaleForTesting(boolean extract) {
- assert (sInstance == null || sInstance.mExtractTask == null)
- : "Must be called before startExtractingResources is called";
- sExtractImplicitLocalePak = extract;
- }
-
- /**
- * Marks all the 'pak' resources, packaged as assets, for extraction during
- * running the tests.
- */
- @VisibleForTesting
- public void setExtractAllPaksAndV8SnapshotForTesting() {
- List<String> pakAndSnapshotFileAssets = new ArrayList<String>();
- AssetManager manager = mContext.getResources().getAssets();
- try {
- String[] files = manager.list("");
- for (String file : files) {
- if (file.endsWith(".pak")) pakAndSnapshotFileAssets.add(file);
- }
- } catch (IOException e) {
- Log.w(LOGTAG, "Exception while accessing assets: " + e.getMessage(), e);
- }
- pakAndSnapshotFileAssets.add("natives_blob.bin");
- pakAndSnapshotFileAssets.add("snapshot_blob.bin");
- setMandatoryPaksToExtract(pakAndSnapshotFileAssets.toArray(
- new String[pakAndSnapshotFileAssets.size()]));
- }
-
- private ResourceExtractor(Context context) {
- mContext = context.getApplicationContext();
- }
-
- /**
- * Synchronously wait for the resource extraction to be completed.
- * <p>
- * This method is bad and you should feel bad for using it.
- *
- * @see #addCompletionCallback(Runnable)
- */
- 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();
- } catch (ExecutionException e2) {
- deleteFiles();
- } catch (InterruptedException e3) {
- deleteFiles();
- }
- }
-
- /**
- * Adds a callback to be notified upon the completion of resource extraction.
- * <p>
- * If the resource task has already completed, the callback will be posted to the UI message
- * queue. Otherwise, it will be executed after all the resources have been extracted.
- * <p>
- * This must be called on the UI thread. The callback will also always be executed on
- * the UI thread.
- *
- * @param callback The callback to be enqueued.
- */
- public void addCompletionCallback(Runnable callback) {
- ThreadUtils.assertOnUiThread();
-
- Handler handler = new Handler(Looper.getMainLooper());
- if (shouldSkipPakExtraction()) {
- handler.post(callback);
- return;
- }
-
- assert mExtractTask != null;
- assert !mExtractTask.isCancelled();
- if (mExtractTask.getStatus() == AsyncTask.Status.FINISHED) {
- handler.post(callback);
- } else {
- mExtractTask.mCompletionCallbacks.add(callback);
- }
- }
-
- /**
- * 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);
- }
-
- private File getAppDataDir() {
- return new File(PathUtils.getDataDirectory(mContext));
- }
-
- private File getOutputDir() {
- return new File(getAppDataDir(), "paks");
- }
-
- /**
- * Pak files (UI strings and other resources) should be updated along with
- * Chrome. A version mismatch can lead to a rather broken user experience.
- * Failing to update the V8 snapshot files will lead to a version mismatch
- * between V8 and the loaded snapshot which will cause V8 to crash, so this
- * is treated as an error. The ICU data (icudtl.dat) is less
- * version-sensitive, but still can lead to malfunction/UX misbehavior. So,
- * we regard failing to update them as an error.
- */
- private void deleteFiles() {
- File icudata = new File(getAppDataDir(), ICU_DATA_FILENAME);
- if (icudata.exists() && !icudata.delete()) {
- Log.e(LOGTAG, "Unable to remove the icudata " + icudata.getName());
- }
- File v8_natives = new File(getAppDataDir(), V8_NATIVES_DATA_FILENAME);
- if (v8_natives.exists() && !v8_natives.delete()) {
- Log.e(LOGTAG,
- "Unable to remove the v8 data " + v8_natives.getName());
- }
- File v8_snapshot = new File(getAppDataDir(), V8_SNAPSHOT_DATA_FILENAME);
- if (v8_snapshot.exists() && !v8_snapshot.delete()) {
- Log.e(LOGTAG,
- "Unable to remove the v8 data " + v8_snapshot.getName());
- }
- File dir = getOutputDir();
- if (dir.exists()) {
- File[] files = dir.listFiles();
- for (File file : files) {
- if (!file.delete()) {
- Log.e(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]);
- }
-}