26 private static final String TAG =
"ResourceExtractor";
27 private static final String TIMESTAMP_PREFIX =
"res_timestamp-";
28 private static final String[] SUPPORTED_ABIS =
Build.SUPPORTED_ABIS;
30 @SuppressWarnings(
"deprecation")
33 if (
Build.VERSION.SDK_INT >= API_LEVELS.API_28) {
34 return packageInfo.getLongVersionCode();
36 return packageInfo.versionCode;
40 private static class ExtractTask
extends AsyncTask<Void, Void, Void> {
41 @NonNull
private final String mDataDirPath;
42 @NonNull
private final HashSet<String> mResources;
43 @NonNull
private final AssetManager mAssetManager;
44 @NonNull
private final String mPackageName;
45 @NonNull
private final PackageManager mPackageManager;
48 @NonNull String dataDirPath,
49 @NonNull HashSet<String> resources,
50 @NonNull String packageName,
51 @NonNull PackageManager packageManager,
52 @NonNull AssetManager assetManager) {
53 mDataDirPath = dataDirPath;
54 mResources = resources;
55 mAssetManager = assetManager;
56 mPackageName = packageName;
57 mPackageManager = packageManager;
61 protected Void doInBackground(Void...
unused) {
62 final File dataDir =
new File(mDataDirPath);
64 final String timestamp = checkTimestamp(dataDir, mPackageManager, mPackageName);
65 if (timestamp ==
null) {
69 deleteFiles(mDataDirPath, mResources);
71 if (!extractAPK(dataDir)) {
75 if (timestamp !=
null) {
77 new File(dataDir, timestamp).createNewFile();
78 }
catch (IOException e) {
79 Log.w(TAG,
"Failed to write resource timestamp");
89 private boolean extractAPK(@NonNull File dataDir) {
90 for (String asset : mResources) {
92 final String resource =
"assets/" + asset;
93 final File
output =
new File(dataDir, asset);
97 if (
output.getParentFile() !=
null) {
98 output.getParentFile().mkdirs();
101 try (InputStream is = mAssetManager.open(asset);
102 OutputStream os =
new FileOutputStream(output)) {
105 if (BuildConfig.DEBUG) {
106 Log.i(TAG,
"Extracted baseline resource " + resource);
108 }
catch (FileNotFoundException fnfe) {
111 }
catch (IOException ioe) {
112 Log.w(TAG,
"Exception unpacking resources: " + ioe.getMessage());
113 deleteFiles(mDataDirPath, mResources);
122 @NonNull
private final String mDataDirPath;
123 @NonNull
private final String mPackageName;
124 @NonNull
private final PackageManager mPackageManager;
125 @NonNull
private final AssetManager mAssetManager;
126 @NonNull
private final HashSet<String> mResources;
127 private ExtractTask mExtractTask;
130 @NonNull String dataDirPath,
131 @NonNull String packageName,
132 @NonNull PackageManager packageManager,
133 @NonNull AssetManager assetManager) {
134 mDataDirPath = dataDirPath;
135 mPackageName = packageName;
136 mPackageManager = packageManager;
137 mAssetManager = assetManager;
138 mResources =
new HashSet<>();
142 mResources.add(resource);
147 mResources.addAll(resources);
154 TAG,
"Attempted to start resource extraction while another extraction was in progress.");
157 new ExtractTask(mDataDirPath, mResources, mPackageName, mPackageManager, mAssetManager);
158 mExtractTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
163 if (mExtractTask ==
null) {
169 }
catch (CancellationException | ExecutionException | InterruptedException e) {
170 deleteFiles(mDataDirPath, mResources);
174 private static String[] getExistingTimestamps(File dataDir) {
176 new FilenameFilter() {
178 public boolean accept(File dir, String
name) {
179 return name.startsWith(TIMESTAMP_PREFIX);
184 private static void deleteFiles(@NonNull String dataDirPath, @NonNull HashSet<String> resources) {
185 final File dataDir =
new File(dataDirPath);
186 for (String resource : resources) {
187 final File file =
new File(dataDir, resource);
192 final String[] existingTimestamps = getExistingTimestamps(dataDir);
193 if (existingTimestamps ==
null) {
196 for (String timestamp : existingTimestamps) {
197 new File(dataDir, timestamp).delete();
203 private static String checkTimestamp(
204 @NonNull File dataDir, @NonNull PackageManager packageManager, @NonNull String packageName) {
205 PackageInfo packageInfo =
null;
208 packageInfo = packageManager.getPackageInfo(packageName, 0);
209 }
catch (PackageManager.NameNotFoundException e) {
210 return TIMESTAMP_PREFIX;
213 if (packageInfo ==
null) {
214 return TIMESTAMP_PREFIX;
217 String expectedTimestamp =
218 TIMESTAMP_PREFIX +
getVersionCode(packageInfo) +
"-" + packageInfo.lastUpdateTime;
220 final String[] existingTimestamps = getExistingTimestamps(dataDir);
222 if (existingTimestamps ==
null) {
223 if (BuildConfig.DEBUG) {
224 Log.i(TAG,
"No extracted resources found");
226 return expectedTimestamp;
229 if (existingTimestamps.length == 1) {
230 if (BuildConfig.DEBUG) {
231 Log.i(TAG,
"Found extracted resources " + existingTimestamps[0]);
235 if (existingTimestamps.length != 1 || !expectedTimestamp.equals(existingTimestamps[0])) {
236 if (BuildConfig.DEBUG) {
237 Log.i(TAG,
"Resource version mismatch " + expectedTimestamp);
239 return expectedTimestamp;
245 private static void copy(@NonNull InputStream in, @NonNull OutputStream out)
throws IOException {
246 byte[] buf =
new byte[16 * 1024];
247 for (
int i; (i = in.read(buf)) >= 0; ) {
248 out.write(buf, 0, i);