Chromium Code Reviews| OLD | NEW |
|---|---|
| 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.content.browser; | 5 package org.chromium.content.browser; |
| 6 | 6 |
| 7 import android.content.Context; | 7 import android.content.Context; |
| 8 import android.content.SharedPreferences; | 8 import android.content.SharedPreferences; |
| 9 import android.content.pm.PackageInfo; | 9 import android.content.pm.PackageInfo; |
| 10 import android.content.pm.PackageManager; | 10 import android.content.pm.PackageManager; |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 29 | 29 |
| 30 /** | 30 /** |
| 31 * Handles extracting the necessary resources bundled in an APK and moving them to a location on | 31 * Handles extracting the necessary resources bundled in an APK and moving them to a location on |
| 32 * the file system accessible from the native code. | 32 * the file system accessible from the native code. |
| 33 */ | 33 */ |
| 34 public class ResourceExtractor { | 34 public class ResourceExtractor { |
| 35 | 35 |
| 36 private static final String LOGTAG = "ResourceExtractor"; | 36 private static final String LOGTAG = "ResourceExtractor"; |
| 37 private static final String LAST_LANGUAGE = "Last language"; | 37 private static final String LAST_LANGUAGE = "Last language"; |
| 38 private static final String PAK_FILENAMES = "Pak filenames"; | 38 private static final String PAK_FILENAMES = "Pak filenames"; |
| 39 private static final String ICU_DATA_FILENAME = "icudtl.dat"; | |
| 39 | 40 |
| 40 private static String[] sMandatoryPaks = null; | 41 private static String[] sMandatoryPaks = null; |
| 41 | 42 |
| 42 // By default, we attempt to extract a pak file for the users | 43 // By default, we attempt to extract a pak file for the users |
| 43 // current device locale. Use setExtractImplicitLocale() to | 44 // current device locale. Use setExtractImplicitLocale() to |
| 44 // change this behavior. | 45 // change this behavior. |
| 45 private static boolean sExtractImplicitLocalePak = true; | 46 private static boolean sExtractImplicitLocalePak = true; |
| 46 | 47 |
| 47 private class ExtractTask extends AsyncTask<Void, Void, Void> { | 48 private class ExtractTask extends AsyncTask<Void, Void, Void> { |
| 48 private static final int BUFFER_SIZE = 16 * 1024; | 49 private static final int BUFFER_SIZE = 16 * 1024; |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 89 } | 90 } |
| 90 | 91 |
| 91 if (sExtractImplicitLocalePak) { | 92 if (sExtractImplicitLocalePak) { |
| 92 if (p.length() > 0) p.append('|'); | 93 if (p.length() > 0) p.append('|'); |
| 93 // As well as the minimum required set of .paks above, we'll als o add all .paks that | 94 // As well as the minimum required set of .paks above, we'll als o add all .paks that |
| 94 // we have for the user's currently selected language. | 95 // we have for the user's currently selected language. |
| 95 | 96 |
| 96 p.append(currentLanguage); | 97 p.append(currentLanguage); |
| 97 p.append("(-\\w+)?\\.pak"); | 98 p.append("(-\\w+)?\\.pak"); |
| 98 } | 99 } |
| 100 | |
| 101 if (p.length() > 0) p.append('|'); | |
| 102 p.append(ICU_DATA_FILENAME); | |
| 103 | |
| 99 Pattern paksToInstall = Pattern.compile(p.toString()); | 104 Pattern paksToInstall = Pattern.compile(p.toString()); |
| 100 | 105 |
| 101 AssetManager manager = mContext.getResources().getAssets(); | 106 AssetManager manager = mContext.getResources().getAssets(); |
| 102 try { | 107 try { |
| 103 // Loop through every asset file that we have in the APK, and lo ok for the | 108 // Loop through every asset file that we have in the APK, and lo ok for the |
| 104 // ones that we need to extract by trying to match the Patterns that we | 109 // ones that we need to extract by trying to match the Patterns that we |
| 105 // created above. | 110 // created above. |
| 106 byte[] buffer = null; | 111 byte[] buffer = null; |
| 107 String[] files = manager.list(""); | 112 String[] files = manager.list(""); |
| 108 for (String file : files) { | 113 for (String file : files) { |
| 109 if (!paksToInstall.matcher(file).matches()) { | 114 if (!paksToInstall.matcher(file).matches()) { |
| 110 continue; | 115 continue; |
| 111 } | 116 } |
| 112 File output = new File(mOutputDir, file); | 117 boolean isICUData = file.equals(ICU_DATA_FILENAME); |
| 118 File output = new File(isICUData ? mAppDataDir : mOutputDir, file); | |
| 113 if (output.exists()) { | 119 if (output.exists()) { |
| 114 continue; | 120 continue; |
| 115 } | 121 } |
| 116 | 122 |
| 117 InputStream is = null; | 123 InputStream is = null; |
| 118 OutputStream os = null; | 124 OutputStream os = null; |
| 119 try { | 125 try { |
| 120 is = manager.open(file); | 126 is = manager.open(file); |
| 121 os = new FileOutputStream(output); | 127 os = new FileOutputStream(output); |
| 122 Log.i(LOGTAG, "Extracting resource " + file); | 128 Log.i(LOGTAG, "Extracting resource " + file); |
| 123 if (buffer == null) { | 129 if (buffer == null) { |
| 124 buffer = new byte[BUFFER_SIZE]; | 130 buffer = new byte[BUFFER_SIZE]; |
| 125 } | 131 } |
| 126 | 132 |
| 127 int count = 0; | 133 int count = 0; |
| 128 while ((count = is.read(buffer, 0, BUFFER_SIZE)) != -1) { | 134 while ((count = is.read(buffer, 0, BUFFER_SIZE)) != -1) { |
| 129 os.write(buffer, 0, count); | 135 os.write(buffer, 0, count); |
| 130 } | 136 } |
| 131 os.flush(); | 137 os.flush(); |
| 132 | 138 |
| 133 // Ensure something reasonable was written. | 139 // Ensure something reasonable was written. |
| 134 if (output.length() == 0) { | 140 if (output.length() == 0) { |
| 135 throw new IOException(file + " extracted with 0 leng th!"); | 141 throw new IOException(file + " extracted with 0 leng th!"); |
| 136 } | 142 } |
| 137 | 143 |
| 138 filenames.add(file); | 144 if (!isICUData) { |
| 145 filenames.add(file); | |
| 146 } else { | |
| 147 // icudata needs to be accessed by a renderer proces s. | |
| 148 output.setReadable(true, false); | |
| 149 } | |
| 139 } finally { | 150 } finally { |
| 140 try { | 151 try { |
| 141 if (is != null) { | 152 if (is != null) { |
| 142 is.close(); | 153 is.close(); |
| 143 } | 154 } |
| 144 } finally { | 155 } finally { |
| 145 if (os != null) { | 156 if (os != null) { |
| 146 os.close(); | 157 os.close(); |
| 147 } | 158 } |
| 148 } | 159 } |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 216 return expectedTimestamp; | 227 return expectedTimestamp; |
| 217 } | 228 } |
| 218 | 229 |
| 219 // timestamp file is already up-to date. | 230 // timestamp file is already up-to date. |
| 220 return null; | 231 return null; |
| 221 } | 232 } |
| 222 } | 233 } |
| 223 | 234 |
| 224 private final Context mContext; | 235 private final Context mContext; |
| 225 private ExtractTask mExtractTask; | 236 private ExtractTask mExtractTask; |
| 237 private final File mAppDataDir; | |
| 226 private final File mOutputDir; | 238 private final File mOutputDir; |
| 227 | 239 |
| 228 private static ResourceExtractor sInstance; | 240 private static ResourceExtractor sInstance; |
| 229 | 241 |
| 230 public static ResourceExtractor get(Context context) { | 242 public static ResourceExtractor get(Context context) { |
| 231 if (sInstance == null) { | 243 if (sInstance == null) { |
| 232 sInstance = new ResourceExtractor(context); | 244 sInstance = new ResourceExtractor(context); |
| 233 } | 245 } |
| 234 return sInstance; | 246 return sInstance; |
| 235 } | 247 } |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 256 * pak files specified in sMandatoryPaks. | 268 * pak files specified in sMandatoryPaks. |
| 257 */ | 269 */ |
| 258 public static void setExtractImplicitLocaleForTesting(boolean extract) { | 270 public static void setExtractImplicitLocaleForTesting(boolean extract) { |
| 259 assert (sInstance == null || sInstance.mExtractTask == null) | 271 assert (sInstance == null || sInstance.mExtractTask == null) |
| 260 : "Must be called before startExtractingResources is called"; | 272 : "Must be called before startExtractingResources is called"; |
| 261 sExtractImplicitLocalePak = extract; | 273 sExtractImplicitLocalePak = extract; |
| 262 } | 274 } |
| 263 | 275 |
| 264 private ResourceExtractor(Context context) { | 276 private ResourceExtractor(Context context) { |
| 265 mContext = context; | 277 mContext = context; |
| 278 mAppDataDir = getAppDataDirFromContext(mContext); | |
| 266 mOutputDir = getOutputDirFromContext(mContext); | 279 mOutputDir = getOutputDirFromContext(mContext); |
| 267 } | 280 } |
| 268 | 281 |
| 269 public void waitForCompletion() { | 282 public void waitForCompletion() { |
| 270 if (shouldSkipPakExtraction()) { | 283 if (shouldSkipExtraction()) { |
| 271 return; | 284 return; |
| 272 } | 285 } |
| 273 | 286 |
| 274 assert mExtractTask != null; | 287 assert mExtractTask != null; |
| 275 | 288 |
| 276 try { | 289 try { |
| 277 mExtractTask.get(); | 290 mExtractTask.get(); |
| 278 } catch (CancellationException e) { | 291 } catch (CancellationException e) { |
| 279 // Don't leave the files in an inconsistent state. | 292 // Don't leave the files in an inconsistent state. |
| 280 deleteFiles(mContext); | 293 deleteFiles(mContext); |
| 281 } catch (ExecutionException e2) { | 294 } catch (ExecutionException e2) { |
| 282 deleteFiles(mContext); | 295 deleteFiles(mContext); |
| 283 } catch (InterruptedException e3) { | 296 } catch (InterruptedException e3) { |
| 284 deleteFiles(mContext); | 297 deleteFiles(mContext); |
| 285 } | 298 } |
| 286 } | 299 } |
| 287 | 300 |
| 288 /** | 301 /** |
| 289 * This will extract the application pak resources in an | 302 * This will extract the application pak resources in an |
| 290 * AsyncTask. Call waitForCompletion() at the point resources | 303 * AsyncTask. Call waitForCompletion() at the point resources |
| 291 * are needed to block until the task completes. | 304 * are needed to block until the task completes. |
| 292 */ | 305 */ |
| 293 public void startExtractingResources() { | 306 public void startExtractingResources() { |
| 294 if (mExtractTask != null) { | 307 if (mExtractTask != null) { |
| 295 return; | 308 return; |
| 296 } | 309 } |
| 297 | 310 |
| 298 if (shouldSkipPakExtraction()) { | 311 if (shouldSkipExtraction()) { |
| 299 return; | 312 return; |
| 300 } | 313 } |
| 301 | 314 |
| 302 mExtractTask = new ExtractTask(); | 315 mExtractTask = new ExtractTask(); |
| 303 mExtractTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | 316 mExtractTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| 304 } | 317 } |
| 305 | 318 |
| 319 public static File getAppDataDirFromContext(Context context) { | |
| 320 return new File(PathUtils.getDataDirectory(context.getApplicationContext ())); | |
| 321 } | |
| 306 public static File getOutputDirFromContext(Context context) { | 322 public static File getOutputDirFromContext(Context context) { |
| 307 return new File(PathUtils.getDataDirectory(context.getApplicationContext ()), "paks"); | 323 return new File(getAppDataDirFromContext(context), "paks"); |
| 308 } | 324 } |
| 309 | 325 |
| 310 public static void deleteFiles(Context context) { | 326 public static void deleteFiles(Context context) { |
| 327 File icudata = new File(getAppDataDirFromContext(context), ICU_DATA_FILE NAME); | |
| 328 if (icudata.exists() && !icudata.delete()) { | |
| 329 Log.w(LOGTAG, "Unable to remove the icudata " + icudata.getName()); | |
| 330 } | |
| 311 File dir = getOutputDirFromContext(context); | 331 File dir = getOutputDirFromContext(context); |
| 312 if (dir.exists()) { | 332 if (dir.exists()) { |
| 313 File[] files = dir.listFiles(); | 333 File[] files = dir.listFiles(); |
| 314 for (File file : files) { | 334 for (File file : files) { |
| 315 if (!file.delete()) { | 335 if (!file.delete()) { |
| 316 Log.w(LOGTAG, "Unable to remove existing resource " + file.g etName()); | 336 Log.w(LOGTAG, "Unable to remove existing resource " + file.g etName()); |
| 317 } | 337 } |
| 318 } | 338 } |
| 319 } | 339 } |
| 320 } | 340 } |
| 321 | 341 |
| 322 /** | 342 /** |
| 323 * Pak extraction not necessarily required by the embedder; we allow them to skip | 343 * Pak extraction not necessarily required by the embedder; we allow them to skip |
| 324 * this process if they call setMandatoryPaksToExtract with a single empty S tring. | 344 * this process if they call setMandatoryPaksToExtract with a single empty S tring and the |
| 345 * icudata is absent. icudtl.dat will be present only when icu_use_data_file _flag == 1, which is | |
| 346 * false for the WebView build. | |
|
benm (inactive)
2014/02/10 21:30:45
i think that if an app using WebView had bundled a
jungshik at Google
2014/02/10 22:10:31
Although that's not likely, I agree that we'd bett
benm (inactive)
2014/02/10 22:28:32
I think that the other use will only happen in the
jungshik at Google
2014/02/10 23:41:46
That's what I expect us to do, but my reading of t
| |
| 325 */ | 347 */ |
| 348 private boolean shouldSkipExtraction() { | |
| 349 AssetManager manager = mContext.getResources().getAssets(); | |
| 350 String[] files = null; | |
| 351 try { | |
| 352 files = manager.list(ICU_DATA_FILENAME); | |
| 353 } catch (IOException e) { | |
| 354 Log.w(LOGTAG, ICU_DATA_FILENAME + " is not found."); | |
| 355 } | |
| 356 return (files == null) && shouldSkipPakExtraction(); | |
| 357 } | |
| 358 | |
| 326 private static boolean shouldSkipPakExtraction() { | 359 private static boolean shouldSkipPakExtraction() { |
| 327 // Must call setMandatoryPaksToExtract before beginning resource extract ion. | 360 // Must call setMandatoryPaksToExtract before beginning resource extract ion. |
| 328 assert sMandatoryPaks != null; | 361 assert sMandatoryPaks != null; |
| 329 return sMandatoryPaks.length == 1 && "".equals(sMandatoryPaks[0]); | 362 return sMandatoryPaks.length == 1 && "".equals(sMandatoryPaks[0]); |
| 330 } | 363 } |
| 331 } | 364 } |
| OLD | NEW |