1a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)// Copyright 2012 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)package org.chromium.content.browser; 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.content.Context; 85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.content.SharedPreferences; 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.content.pm.PackageInfo; 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.content.pm.PackageManager; 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.content.res.AssetManager; 125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.os.AsyncTask; 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.preference.PreferenceManager; 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.util.Log; 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import org.chromium.base.PathUtils; 17f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import org.chromium.ui.base.LocalizationUtils; 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.io.File; 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.io.FileOutputStream; 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.io.FilenameFilter; 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.io.IOException; 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.io.InputStream; 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.io.OutputStream; 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.util.HashSet; 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.util.concurrent.CancellationException; 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.util.concurrent.ExecutionException; 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.util.regex.Pattern; 295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Handles extracting the necessary resources bundled in an APK and moving them to a location on 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * the file system accessible from the native code. 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)public class ResourceExtractor { 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private static final String LOGTAG = "ResourceExtractor"; 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private static final String LAST_LANGUAGE = "Last language"; 385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private static final String PAK_FILENAMES = "Pak filenames"; 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private static String[] sMandatoryPaks = null; 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // By default, we attempt to extract a pak file for the users 432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // current device locale. Use setExtractImplicitLocale() to 442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // change this behavior. 452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) private static boolean sExtractImplicitLocalePak = true; 462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private class ExtractTask extends AsyncTask<Void, Void, Void> { 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private static final int BUFFER_SIZE = 16 * 1024; 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public ExtractTask() { 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @Override 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) protected Void doInBackground(Void... unused) { 555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!mOutputDir.exists() && !mOutputDir.mkdirs()) { 565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Log.e(LOGTAG, "Unable to create pak resources directory!"); 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return null; 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) String timestampFile = checkPakTimestamp(); 615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (timestampFile != null) { 625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) deleteFiles(mContext); 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) HashSet<String> filenames = (HashSet<String>) prefs.getStringSet( 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) PAK_FILENAMES, new HashSet<String>()); 68c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) String currentLocale = LocalizationUtils.getDefaultLocale(); 692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) String currentLanguage = currentLocale.split("-", 2)[0]; 702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (prefs.getString(LAST_LANGUAGE, "").equals(currentLanguage) 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) && filenames.size() >= sMandatoryPaks.length) { 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) boolean filesPresent = true; 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (String file : filenames) { 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!new File(mOutputDir, file).exists()) { 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) filesPresent = false; 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) break; 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (filesPresent) return null; 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) prefs.edit().putString(LAST_LANGUAGE, currentLanguage).apply(); 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) StringBuilder p = new StringBuilder(); 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (String mandatoryPak : sMandatoryPaks) { 872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (p.length() > 0) p.append('|'); 882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) p.append("\\Q" + mandatoryPak + "\\E"); 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (sExtractImplicitLocalePak) { 922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (p.length() > 0) p.append('|'); 932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // As well as the minimum required set of .paks above, we'll also add all .paks that 942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // we have for the user's currently selected language. 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) p.append(currentLanguage); 972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) p.append("(-\\w+)?\\.pak"); 982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Pattern paksToInstall = Pattern.compile(p.toString()); 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) AssetManager manager = mContext.getResources().getAssets(); 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try { 1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Loop through every asset file that we have in the APK, and look for the 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // ones that we need to extract by trying to match the Patterns that we 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // created above. 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) byte[] buffer = null; 1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) String[] files = manager.list(""); 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (String file : files) { 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!paksToInstall.matcher(file).matches()) { 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) continue; 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) File output = new File(mOutputDir, file); 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (output.exists()) { 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) continue; 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) InputStream is = null; 1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) OutputStream os = null; 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try { 1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) is = manager.open(file); 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) os = new FileOutputStream(output); 1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Log.i(LOGTAG, "Extracting resource " + file); 1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (buffer == null) { 1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) buffer = new byte[BUFFER_SIZE]; 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int count = 0; 1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) while ((count = is.read(buffer, 0, BUFFER_SIZE)) != -1) { 1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) os.write(buffer, 0, count); 1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) os.flush(); 1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Ensure something reasonable was written. 1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (output.length() == 0) { 1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) throw new IOException(file + " extracted with 0 length!"); 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) filenames.add(file); 1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } finally { 1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try { 1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (is != null) { 1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) is.close(); 1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } finally { 1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (os != null) { 1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) os.close(); 1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } catch (IOException e) { 1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // TODO(benm): See crbug/152413. 1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Try to recover here, can we try again after deleting files instead of 1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // returning null? It might be useful to gather UMA here too to track if 1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // this happens with regularity. 1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Log.w(LOGTAG, "Exception unpacking required pak resources: " + e.getMessage()); 1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) deleteFiles(mContext); 1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return null; 1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Finished, write out a timestamp file if we need to. 1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (timestampFile != null) { 1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try { 1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new File(mOutputDir, timestampFile).createNewFile(); 1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } catch (IOException e) { 1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Worst case we don't write a timestamp, so we'll re-extract the resource 1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // paks next start up. 1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Log.w(LOGTAG, "Failed to write resource pak timestamp!"); 1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // TODO(yusufo): Figure out why remove is required here. 1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) prefs.edit().remove(PAK_FILENAMES).apply(); 1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) prefs.edit().putStringSet(PAK_FILENAMES, filenames).apply(); 1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return null; 1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Looks for a timestamp file on disk that indicates the version of the APK that 1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // the resource paks were extracted from. Returns null if a timestamp was found 1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // and it indicates that the resources match the current APK. Otherwise returns 1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // a String that represents the filename of a timestamp to create. 1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Note that we do this to avoid adding a BroadcastReceiver on 1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // android.content.Intent#ACTION_PACKAGE_CHANGED as that causes process churn 1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // on (re)installation of *all* APK files. 1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private String checkPakTimestamp() { 1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) final String TIMESTAMP_PREFIX = "pak_timestamp-"; 1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) PackageManager pm = mContext.getPackageManager(); 1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) PackageInfo pi = null; 1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try { 1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) pi = pm.getPackageInfo(mContext.getPackageName(), 0); 1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } catch (PackageManager.NameNotFoundException e) { 1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return TIMESTAMP_PREFIX; 1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (pi == null) { 1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return TIMESTAMP_PREFIX; 1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) String expectedTimestamp = TIMESTAMP_PREFIX + pi.versionCode + "-" + pi.lastUpdateTime; 2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) String[] timestamps = mOutputDir.list(new FilenameFilter() { 2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @Override 2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public boolean accept(File dir, String name) { 2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return name.startsWith(TIMESTAMP_PREFIX); 2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }); 2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (timestamps.length != 1) { 2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // If there's no timestamp, nuke to be safe as we can't tell the age of the files. 2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // If there's multiple timestamps, something's gone wrong so nuke. 2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return expectedTimestamp; 2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!expectedTimestamp.equals(timestamps[0])) { 2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return expectedTimestamp; 2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // timestamp file is already up-to date. 2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return null; 2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 224f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) private final Context mContext; 2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private ExtractTask mExtractTask; 226f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) private final File mOutputDir; 2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private static ResourceExtractor sInstance; 2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public static ResourceExtractor get(Context context) { 2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (sInstance == null) { 2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) sInstance = new ResourceExtractor(context); 2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return sInstance; 2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Specifies the .pak files that should be extracted from the APK's asset resources directory 2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * and moved to {@link #getOutputDirFromContext(Context)}. 240ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch * @param mandatoryPaks The list of pak files to be loaded. If no pak files are 241ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch * required, pass a single empty string. 2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public static void setMandatoryPaksToExtract(String... mandatoryPaks) { 2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) assert (sInstance == null || sInstance.mExtractTask == null) 2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) : "Must be called before startExtractingResources is called"; 2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) sMandatoryPaks = mandatoryPaks; 2472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 2492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) /** 2512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * By default the ResourceExtractor will attempt to extract a pak resource for the users 2522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * currently specified locale. This behavior can be changed with this function and is 2532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * only needed by tests. 2542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param extract False if we should not attempt to extract a pak file for 2552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * the users currently selected locale and try to extract only the 2562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * pak files specified in sMandatoryPaks. 2572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */ 2582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) public static void setExtractImplicitLocaleForTesting(boolean extract) { 2592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) assert (sInstance == null || sInstance.mExtractTask == null) 2602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) : "Must be called before startExtractingResources is called"; 2612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) sExtractImplicitLocalePak = extract; 2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private ResourceExtractor(Context context) { 2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) mContext = context; 2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) mOutputDir = getOutputDirFromContext(mContext); 2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public void waitForCompletion() { 270ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch if (shouldSkipPakExtraction()) { 271ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch return; 272ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch } 273ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch 2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) assert mExtractTask != null; 2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try { 2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) mExtractTask.get(); 278a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } catch (CancellationException e) { 2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Don't leave the files in an inconsistent state. 2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) deleteFiles(mContext); 281a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } catch (ExecutionException e2) { 2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) deleteFiles(mContext); 283a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } catch (InterruptedException e3) { 2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) deleteFiles(mContext); 2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 288f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) /** 289f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) * This will extract the application pak resources in an 290f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) * AsyncTask. Call waitForCompletion() at the point resources 291f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) * are needed to block until the task completes. 292f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) */ 2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public void startExtractingResources() { 2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (mExtractTask != null) { 2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 298ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch if (shouldSkipPakExtraction()) { 299ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch return; 300ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch } 301ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch 3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) mExtractTask = new ExtractTask(); 3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) mExtractTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public static File getOutputDirFromContext(Context context) { 3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return new File(PathUtils.getDataDirectory(context.getApplicationContext()), "paks"); 3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public static void deleteFiles(Context context) { 3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) File dir = getOutputDirFromContext(context); 3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (dir.exists()) { 3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) File[] files = dir.listFiles(); 3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (File file : files) { 3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!file.delete()) { 3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Log.w(LOGTAG, "Unable to remove existing resource " + file.getName()); 3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 321ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch 322ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch /** 323ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch * Pak extraction not necessarily required by the embedder; we allow them to skip 324ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch * this process if they call setMandatoryPaksToExtract with a single empty String. 325ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch */ 326ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch private static boolean shouldSkipPakExtraction() { 327ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch // Must call setMandatoryPaksToExtract before beginning resource extraction. 328ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch assert sMandatoryPaks != null; 329ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch return sMandatoryPaks.length == 1 && "".equals(sMandatoryPaks[0]); 330ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch } 3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 332