Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(129)

Side by Side Diff: base/android/java/src/org/chromium/base/ResourceExtractor.java

Issue 1193113004: Revert of Android: Store language .pak files in res/raw rather than assets (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@load-from-apk-v8
Patch Set: Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2012 The Chromium Authors. All rights reserved. 1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.base; 5 package org.chromium.base;
6 6
7 import android.annotation.TargetApi; 7 import android.annotation.TargetApi;
8 import android.content.Context; 8 import android.content.Context;
9 import android.content.SharedPreferences;
9 import android.content.pm.PackageInfo; 10 import android.content.pm.PackageInfo;
10 import android.content.pm.PackageManager; 11 import android.content.pm.PackageManager;
11 import android.content.res.AssetManager; 12 import android.content.res.AssetManager;
12 import android.content.res.Resources;
13 import android.content.res.TypedArray;
14 import android.os.AsyncTask; 13 import android.os.AsyncTask;
15 import android.os.Build; 14 import android.os.Build;
16 import android.os.Handler; 15 import android.os.Handler;
17 import android.os.Looper; 16 import android.os.Looper;
18 import android.os.Trace; 17 import android.os.Trace;
18 import android.preference.PreferenceManager;
19 import android.util.Log; 19 import android.util.Log;
20 20
21 import java.io.File; 21 import java.io.File;
22 import java.io.FileOutputStream; 22 import java.io.FileOutputStream;
23 import java.io.FilenameFilter; 23 import java.io.FilenameFilter;
24 import java.io.IOException; 24 import java.io.IOException;
25 import java.io.InputStream; 25 import java.io.InputStream;
26 import java.io.OutputStream; 26 import java.io.OutputStream;
27 import java.util.ArrayList; 27 import java.util.ArrayList;
28 import java.util.List; 28 import java.util.List;
29 import java.util.Locale;
30 import java.util.concurrent.CancellationException; 29 import java.util.concurrent.CancellationException;
31 import java.util.concurrent.ExecutionException; 30 import java.util.concurrent.ExecutionException;
31 import java.util.regex.Pattern;
32 32
33 /** 33 /**
34 * Handles extracting the necessary resources bundled in an APK and moving them to a location on 34 * Handles extracting the necessary resources bundled in an APK and moving them to a location on
35 * the file system accessible from the native code. 35 * the file system accessible from the native code.
36 */ 36 */
37 public class ResourceExtractor { 37 public class ResourceExtractor {
38 38
39 private static final String LOGTAG = "ResourceExtractor"; 39 private static final String LOGTAG = "ResourceExtractor";
40 private static final String LAST_LANGUAGE = "Last language";
41 private static final String PAK_FILENAMES_LEGACY_NOREUSE = "Pak filenames";
40 private static final String ICU_DATA_FILENAME = "icudtl.dat"; 42 private static final String ICU_DATA_FILENAME = "icudtl.dat";
41 private static final String V8_NATIVES_DATA_FILENAME = "natives_blob.bin"; 43 private static final String V8_NATIVES_DATA_FILENAME = "natives_blob.bin";
42 private static final String V8_SNAPSHOT_DATA_FILENAME = "snapshot_blob.bin"; 44 private static final String V8_SNAPSHOT_DATA_FILENAME = "snapshot_blob.bin";
43 45
44 private static String[] sMandatoryPaks = null; 46 private static String[] sMandatoryPaks = null;
45 private static int sLocalePaksResId = -1;
46 47
47 /** 48 // By default, we attempt to extract a pak file for the users
48 * Applies the reverse mapping done by locale_pak_resources.py. 49 // current device locale. Use setExtractImplicitLocale() to
49 */ 50 // change this behavior.
50 private static String toChromeLocaleName(String srcFileName) { 51 private static boolean sExtractImplicitLocalePak = true;
51 String[] parts = srcFileName.split("_"); 52
52 if (parts.length > 1) { 53 private static boolean isAppDataFile(String file) {
53 int dotIdx = parts[1].indexOf('.'); 54 return ICU_DATA_FILENAME.equals(file)
54 return parts[0] + "-" + parts[1].substring(0, dotIdx).toUpperCase(Lo cale.ENGLISH) 55 || V8_NATIVES_DATA_FILENAME.equals(file)
55 + parts[1].substring(dotIdx); 56 || V8_SNAPSHOT_DATA_FILENAME.equals(file);
56 }
57 return srcFileName;
58 } 57 }
59 58
60 private class ExtractTask extends AsyncTask<Void, Void, Void> { 59 private class ExtractTask extends AsyncTask<Void, Void, Void> {
61 private static final int BUFFER_SIZE = 16 * 1024; 60 private static final int BUFFER_SIZE = 16 * 1024;
62 61
63 private final List<Runnable> mCompletionCallbacks = new ArrayList<Runnab le>(); 62 private final List<Runnable> mCompletionCallbacks = new ArrayList<Runnab le>();
64 63
65 private void extractResourceHelper(InputStream is, File outFile, byte[] buffer) 64 public ExtractTask() {
66 throws IOException {
67 OutputStream os = null;
68 try {
69 os = new FileOutputStream(outFile);
70 Log.i(LOGTAG, "Extracting resource " + outFile);
71
72 int count = 0;
73 while ((count = is.read(buffer, 0, BUFFER_SIZE)) != -1) {
74 os.write(buffer, 0, count);
75 }
76 os.flush();
77
78 // Ensure something reasonable was written.
79 if (outFile.length() == 0) {
80 throw new IOException(outFile + " extracted with 0 length!") ;
81 }
82 } finally {
83 try {
84 if (is != null) {
85 is.close();
86 }
87 } finally {
88 if (os != null) {
89 os.close();
90 }
91 }
92 }
93 } 65 }
94 66
95 private void doInBackgroundImpl() { 67 private void doInBackgroundImpl() {
96 final File outputDir = getOutputDir(); 68 final File outputDir = getOutputDir();
69 final File appDataDir = getAppDataDir();
97 if (!outputDir.exists() && !outputDir.mkdirs()) { 70 if (!outputDir.exists() && !outputDir.mkdirs()) {
98 Log.e(LOGTAG, "Unable to create pak resources directory!"); 71 Log.e(LOGTAG, "Unable to create pak resources directory!");
99 return; 72 return;
100 } 73 }
101 74
102 String timestampFile = null; 75 String timestampFile = null;
103 beginTraceSection("checkPakTimeStamp"); 76 beginTraceSection("checkPakTimeStamp");
104 try { 77 try {
105 timestampFile = checkPakTimestamp(outputDir); 78 timestampFile = checkPakTimestamp(outputDir);
106 } finally { 79 } finally {
107 endTraceSection(); 80 endTraceSection();
108 } 81 }
109 if (timestampFile != null) { 82 if (timestampFile != null) {
110 deleteFiles(); 83 deleteFiles();
111 } 84 }
112 85
86 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferen ces(mContext);
87 String currentLocale = LocaleUtils.getDefaultLocale();
88 String currentLanguage = currentLocale.split("-", 2)[0];
89 // If everything we need is already there (and the locale hasn't
90 // changed), quick exit.
91 if (prefs.getString(LAST_LANGUAGE, "").equals(currentLanguage)) {
92 boolean filesPresent = true;
93 for (String file : sMandatoryPaks) {
94 File directory = isAppDataFile(file) ? appDataDir : outputDi r;
95 if (!new File(directory, file).exists()) {
96 filesPresent = false;
97 break;
98 }
99 }
100 if (filesPresent) return;
101 } else {
102 prefs.edit().putString(LAST_LANGUAGE, currentLanguage).apply();
103 }
104
105 StringBuilder p = new StringBuilder();
106 for (String mandatoryPak : sMandatoryPaks) {
107 if (p.length() > 0) p.append('|');
108 p.append("\\Q" + mandatoryPak + "\\E");
109 }
110
111 if (sExtractImplicitLocalePak) {
112 if (p.length() > 0) p.append('|');
113 // As well as the minimum required set of .paks above, we'll
114 // also add all .paks that we have for the user's currently
115 // selected language.
116 p.append(currentLanguage);
117 p.append("(-\\w+)?\\.pak");
118 }
119
120 Pattern paksToInstall = Pattern.compile(p.toString());
121
122 AssetManager manager = mContext.getResources().getAssets();
113 beginTraceSection("WalkAssets"); 123 beginTraceSection("WalkAssets");
114 AssetManager assetManager = mContext.getAssets();
115 byte[] buffer = new byte[BUFFER_SIZE];
116 try { 124 try {
117 // Extract all files that don't already exist. 125 // Loop through every asset file that we have in the APK, and lo ok for the
118 for (String fileName : sMandatoryPaks) { 126 // ones that we need to extract by trying to match the Patterns that we
119 File output = new File(outputDir, fileName); 127 // created above.
128 byte[] buffer = null;
129 String[] files = manager.list("");
130 for (String file : files) {
131 if (!paksToInstall.matcher(file).matches()) {
132 continue;
133 }
134 File output = new File(isAppDataFile(file) ? appDataDir : ou tputDir, file);
120 if (output.exists()) { 135 if (output.exists()) {
121 continue; 136 continue;
122 } 137 }
138
139 InputStream is = null;
140 OutputStream os = null;
123 beginTraceSection("ExtractResource"); 141 beginTraceSection("ExtractResource");
124 InputStream inputStream = assetManager.open(fileName);
125 try { 142 try {
126 extractResourceHelper(inputStream, output, buffer); 143 is = manager.open(file);
127 } finally { 144 os = new FileOutputStream(output);
128 endTraceSection(); // ExtractResource 145 Log.i(LOGTAG, "Extracting resource " + file);
129 } 146 if (buffer == null) {
130 } 147 buffer = new byte[BUFFER_SIZE];
148 }
131 149
132 if (sLocalePaksResId != 0) { 150 int count = 0;
133 // locale_paks yields the current language's pak file paths. 151 while ((count = is.read(buffer, 0, BUFFER_SIZE)) != -1) {
134 Resources resources = mContext.getResources(); 152 os.write(buffer, 0, count);
135 TypedArray resIds = resources.obtainTypedArray(sLocalePaksRe sId); 153 }
136 try { 154 os.flush();
137 int len = resIds.length(); 155
138 for (int i = 0; i < len; ++i) { 156 // Ensure something reasonable was written.
139 int resId = resIds.getResourceId(i, 0); 157 if (output.length() == 0) {
140 String resPath = resources.getString(resId); 158 throw new IOException(file + " extracted with 0 leng th!");
141 String srcBaseName = new File(resPath).getName(); 159 }
142 String dstBaseName = toChromeLocaleName(srcBaseName) ; 160
143 File output = new File(outputDir, dstBaseName); 161 if (isAppDataFile(file)) {
144 if (output.exists()) { 162 // icu and V8 data need to be accessed by a renderer
145 continue; 163 // process.
146 } 164 output.setReadable(true, false);
147 beginTraceSection("ExtractResource");
148 InputStream inputStream = resources.openRawResource( resId);
149 try {
150 extractResourceHelper(inputStream, output, buffe r);
151 } finally {
152 endTraceSection(); // ExtractResource
153 }
154 } 165 }
155 } finally { 166 } finally {
156 resIds.recycle(); 167 try {
168 if (is != null) {
169 is.close();
170 }
171 } finally {
172 if (os != null) {
173 os.close();
174 }
175 endTraceSection(); // ExtractResource
176 }
157 } 177 }
158 } 178 }
159 } catch (IOException e) { 179 } catch (IOException e) {
160 // TODO(benm): See crbug/152413. 180 // TODO(benm): See crbug/152413.
161 // Try to recover here, can we try again after deleting files in stead of 181 // Try to recover here, can we try again after deleting files in stead of
162 // returning null? It might be useful to gather UMA here too to track if 182 // returning null? It might be useful to gather UMA here too to track if
163 // this happens with regularity. 183 // this happens with regularity.
164 Log.w(LOGTAG, "Exception unpacking required pak resources: " + e .getMessage()); 184 Log.w(LOGTAG, "Exception unpacking required pak resources: " + e .getMessage());
165 deleteFiles(); 185 deleteFiles();
166 return; 186 return;
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after
276 private static ResourceExtractor sInstance; 296 private static ResourceExtractor sInstance;
277 297
278 public static ResourceExtractor get(Context context) { 298 public static ResourceExtractor get(Context context) {
279 if (sInstance == null) { 299 if (sInstance == null) {
280 sInstance = new ResourceExtractor(context); 300 sInstance = new ResourceExtractor(context);
281 } 301 }
282 return sInstance; 302 return sInstance;
283 } 303 }
284 304
285 /** 305 /**
286 * Specifies the files that should be extracted from the APK. 306 * Specifies the .pak files that should be extracted from the APK's asset re sources directory
287 * and moved to {@link #getOutputDirFromContext(Context)}. 307 * and moved to {@link #getOutputDirFromContext(Context)}.
288 * @param localePaksResId Resource ID for the locale_paks string array. Pass 308 * @param mandatoryPaks The list of pak files to be loaded. If no pak files are
289 * in 0 to disable locale pak extraction. 309 * required, pass a single empty string.
290 * @param paths The list of paths to be extracted.
291 */ 310 */
292 public static void setMandatoryPaksToExtract(int localePaksResId, String... paths) { 311 public static void setMandatoryPaksToExtract(String... mandatoryPaks) {
293 // TODO(agrieve): Remove the need to call this once all files are loaded from the apk. 312 // TODO(agrieve): Remove the need to call this once all files are loaded from the apk.
294 assert (sInstance == null || sInstance.mExtractTask == null) 313 assert (sInstance == null || sInstance.mExtractTask == null)
295 : "Must be called before startExtractingResources is called"; 314 : "Must be called before startExtractingResources is called";
296 sLocalePaksResId = localePaksResId; 315 sMandatoryPaks = mandatoryPaks;
297 sMandatoryPaks = paths; 316
298 } 317 }
299 318
300 /** 319 /**
320 * By default the ResourceExtractor will attempt to extract a pak resource f or the users
321 * currently specified locale. This behavior can be changed with this functi on and is
322 * only needed by tests.
323 * @param extract False if we should not attempt to extract a pak file for
324 * the users currently selected locale and try to extract only the
325 * pak files specified in sMandatoryPaks.
326 */
327 @VisibleForTesting
328 public static void setExtractImplicitLocaleForTesting(boolean extract) {
329 assert (sInstance == null || sInstance.mExtractTask == null)
330 : "Must be called before startExtractingResources is called";
331 sExtractImplicitLocalePak = extract;
332 }
333
334 /**
301 * Marks all the 'pak' resources, packaged as assets, for extraction during 335 * Marks all the 'pak' resources, packaged as assets, for extraction during
302 * running the tests. 336 * running the tests.
303 */ 337 */
304 @VisibleForTesting 338 @VisibleForTesting
305 public void setExtractAllPaksAndV8SnapshotForTesting() { 339 public void setExtractAllPaksAndV8SnapshotForTesting() {
306 List<String> pakAndSnapshotFileAssets = new ArrayList<String>(); 340 List<String> pakAndSnapshotFileAssets = new ArrayList<String>();
307 AssetManager manager = mContext.getResources().getAssets(); 341 AssetManager manager = mContext.getResources().getAssets();
308 try { 342 try {
309 String[] files = manager.list(""); 343 String[] files = manager.list("");
310 for (String file : files) { 344 for (String file : files) {
311 if (file.endsWith(".pak")) pakAndSnapshotFileAssets.add(file); 345 if (file.endsWith(".pak")) pakAndSnapshotFileAssets.add(file);
312 } 346 }
313 } catch (IOException e) { 347 } catch (IOException e) {
314 Log.w(LOGTAG, "Exception while accessing assets: " + e.getMessage(), e); 348 Log.w(LOGTAG, "Exception while accessing assets: " + e.getMessage(), e);
315 } 349 }
316 setMandatoryPaksToExtract(0, pakAndSnapshotFileAssets.toArray( 350 setMandatoryPaksToExtract(pakAndSnapshotFileAssets.toArray(
317 new String[pakAndSnapshotFileAssets.size()])); 351 new String[pakAndSnapshotFileAssets.size()]));
318 } 352 }
319 353
320 private ResourceExtractor(Context context) { 354 private ResourceExtractor(Context context) {
321 mContext = context.getApplicationContext(); 355 mContext = context.getApplicationContext();
322 } 356 }
323 357
324 /** 358 /**
325 * Synchronously wait for the resource extraction to be completed. 359 * Synchronously wait for the resource extraction to be completed.
326 * <p> 360 * <p>
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
379 /** 413 /**
380 * This will extract the application pak resources in an 414 * This will extract the application pak resources in an
381 * AsyncTask. Call waitForCompletion() at the point resources 415 * AsyncTask. Call waitForCompletion() at the point resources
382 * are needed to block until the task completes. 416 * are needed to block until the task completes.
383 */ 417 */
384 public void startExtractingResources() { 418 public void startExtractingResources() {
385 if (mExtractTask != null) { 419 if (mExtractTask != null) {
386 return; 420 return;
387 } 421 }
388 422
389 // If a previous release extracted resources, and the current release do es not,
390 // deleteFiles() will not run and some files will be left. This currentl y
391 // can happen for ContentShell, but not for Chrome proper, since we alwa ys extract
392 // locale pak files.
393 if (shouldSkipPakExtraction()) { 423 if (shouldSkipPakExtraction()) {
394 return; 424 return;
395 } 425 }
396 426
397 mExtractTask = new ExtractTask(); 427 mExtractTask = new ExtractTask();
398 mExtractTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 428 mExtractTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
399 } 429 }
400 430
401 private File getAppDataDir() { 431 private File getAppDataDir() {
402 return new File(PathUtils.getDataDirectory(mContext)); 432 return new File(PathUtils.getDataDirectory(mContext));
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
435 File[] files = dir.listFiles(); 465 File[] files = dir.listFiles();
436 for (File file : files) { 466 for (File file : files) {
437 if (!file.delete()) { 467 if (!file.delete()) {
438 Log.e(LOGTAG, "Unable to remove existing resource " + file.g etName()); 468 Log.e(LOGTAG, "Unable to remove existing resource " + file.g etName());
439 } 469 }
440 } 470 }
441 } 471 }
442 } 472 }
443 473
444 /** 474 /**
445 * Pak extraction not necessarily required by the embedder. 475 * Pak extraction not necessarily required by the embedder; we allow them to skip
476 * this process if they call setMandatoryPaksToExtract with a single empty S tring.
446 */ 477 */
447 private static boolean shouldSkipPakExtraction() { 478 private static boolean shouldSkipPakExtraction() {
448 assert (sLocalePaksResId != -1 && sMandatoryPaks != null) 479 // Must call setMandatoryPaksToExtract before beginning resource extract ion.
449 : "setMandatoryPaksToExtract() must be called before startExtracting Resources()"; 480 assert sMandatoryPaks != null;
450 return sMandatoryPaks.length == 0 && sLocalePaksResId == 0; 481 return sMandatoryPaks.length == 1 && "".equals(sMandatoryPaks[0]);
451 } 482 }
452 } 483 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698