Index: base/android/java/src/org/chromium/base/ResourceExtractor.java |
diff --git a/base/android/java/src/org/chromium/base/ResourceExtractor.java b/base/android/java/src/org/chromium/base/ResourceExtractor.java |
index 584dfae527eab0b1485873b289ec7a0ed0c627c4..dd654ff6efb67fe4b05333701e21f418fcc9f6c7 100644 |
--- a/base/android/java/src/org/chromium/base/ResourceExtractor.java |
+++ b/base/android/java/src/org/chromium/base/ResourceExtractor.java |
@@ -6,16 +6,16 @@ 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.content.res.Resources; |
-import android.content.res.TypedArray; |
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; |
@@ -26,9 +26,9 @@ import java.io.InputStream; |
import java.io.OutputStream; |
import java.util.ArrayList; |
import java.util.List; |
-import java.util.Locale; |
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 |
@@ -37,24 +37,23 @@ import java.util.concurrent.ExecutionException; |
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; |
- private static int sLocalePaksResId = -1; |
- /** |
- * Applies the reverse mapping done by locale_pak_resources.py. |
- */ |
- private static String toChromeLocaleName(String srcFileName) { |
- String[] parts = srcFileName.split("_"); |
- if (parts.length > 1) { |
- int dotIdx = parts[1].indexOf('.'); |
- return parts[0] + "-" + parts[1].substring(0, dotIdx).toUpperCase(Locale.ENGLISH) |
- + parts[1].substring(dotIdx); |
- } |
- return srcFileName; |
+ // 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> { |
@@ -62,38 +61,12 @@ public class ResourceExtractor { |
private final List<Runnable> mCompletionCallbacks = new ArrayList<Runnable>(); |
- private void extractResourceHelper(InputStream is, File outFile, byte[] buffer) |
- throws IOException { |
- OutputStream os = null; |
- try { |
- os = new FileOutputStream(outFile); |
- Log.i(LOGTAG, "Extracting resource " + outFile); |
- |
- 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 (outFile.length() == 0) { |
- throw new IOException(outFile + " extracted with 0 length!"); |
- } |
- } finally { |
- try { |
- if (is != null) { |
- is.close(); |
- } |
- } finally { |
- if (os != null) { |
- os.close(); |
- } |
- } |
- } |
+ 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; |
@@ -110,50 +83,97 @@ public class ResourceExtractor { |
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"); |
- AssetManager assetManager = mContext.getAssets(); |
- byte[] buffer = new byte[BUFFER_SIZE]; |
try { |
- // Extract all files that don't already exist. |
- for (String fileName : sMandatoryPaks) { |
- File output = new File(outputDir, fileName); |
+ // 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"); |
- InputStream inputStream = assetManager.open(fileName); |
try { |
- extractResourceHelper(inputStream, output, buffer); |
- } finally { |
- endTraceSection(); // ExtractResource |
- } |
- } |
+ is = manager.open(file); |
+ os = new FileOutputStream(output); |
+ Log.i(LOGTAG, "Extracting resource " + file); |
+ if (buffer == null) { |
+ buffer = new byte[BUFFER_SIZE]; |
+ } |
- if (sLocalePaksResId != 0) { |
- // locale_paks yields the current language's pak file paths. |
- Resources resources = mContext.getResources(); |
- TypedArray resIds = resources.obtainTypedArray(sLocalePaksResId); |
- try { |
- int len = resIds.length(); |
- for (int i = 0; i < len; ++i) { |
- int resId = resIds.getResourceId(i, 0); |
- String resPath = resources.getString(resId); |
- String srcBaseName = new File(resPath).getName(); |
- String dstBaseName = toChromeLocaleName(srcBaseName); |
- File output = new File(outputDir, dstBaseName); |
- if (output.exists()) { |
- continue; |
+ 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(); |
} |
- beginTraceSection("ExtractResource"); |
- InputStream inputStream = resources.openRawResource(resId); |
- try { |
- extractResourceHelper(inputStream, output, buffer); |
- } finally { |
- endTraceSection(); // ExtractResource |
+ } finally { |
+ if (os != null) { |
+ os.close(); |
} |
+ endTraceSection(); // ExtractResource |
} |
- } finally { |
- resIds.recycle(); |
} |
} |
} catch (IOException e) { |
@@ -283,18 +303,32 @@ public class ResourceExtractor { |
} |
/** |
- * Specifies the files that should be extracted from the APK. |
+ * Specifies the .pak files that should be extracted from the APK's asset resources directory |
* and moved to {@link #getOutputDirFromContext(Context)}. |
- * @param localePaksResId Resource ID for the locale_paks string array. Pass |
- * in 0 to disable locale pak extraction. |
- * @param paths The list of paths to be extracted. |
+ * @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(int localePaksResId, String... paths) { |
+ public static void setMandatoryPaksToExtract(String... mandatoryPaks) { |
// TODO(agrieve): Remove the need to call this once all files are loaded from the apk. |
assert (sInstance == null || sInstance.mExtractTask == null) |
: "Must be called before startExtractingResources is called"; |
- sLocalePaksResId = localePaksResId; |
- sMandatoryPaks = paths; |
+ 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; |
} |
/** |
@@ -313,7 +347,7 @@ public class ResourceExtractor { |
} catch (IOException e) { |
Log.w(LOGTAG, "Exception while accessing assets: " + e.getMessage(), e); |
} |
- setMandatoryPaksToExtract(0, pakAndSnapshotFileAssets.toArray( |
+ setMandatoryPaksToExtract(pakAndSnapshotFileAssets.toArray( |
new String[pakAndSnapshotFileAssets.size()])); |
} |
@@ -386,10 +420,6 @@ public class ResourceExtractor { |
return; |
} |
- // If a previous release extracted resources, and the current release does not, |
- // deleteFiles() will not run and some files will be left. This currently |
- // can happen for ContentShell, but not for Chrome proper, since we always extract |
- // locale pak files. |
if (shouldSkipPakExtraction()) { |
return; |
} |
@@ -442,11 +472,12 @@ public class ResourceExtractor { |
} |
/** |
- * Pak extraction not necessarily required by the embedder. |
+ * 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() { |
- assert (sLocalePaksResId != -1 && sMandatoryPaks != null) |
- : "setMandatoryPaksToExtract() must be called before startExtractingResources()"; |
- return sMandatoryPaks.length == 0 && sLocalePaksResId == 0; |
+ // Must call setMandatoryPaksToExtract before beginning resource extraction. |
+ assert sMandatoryPaks != null; |
+ return sMandatoryPaks.length == 1 && "".equals(sMandatoryPaks[0]); |
} |
} |