Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
ResourceExtractor.java
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package io.flutter.embedding.engine.loader;
6
7import static io.flutter.Build.API_LEVELS;
8
9import android.content.pm.PackageInfo;
10import android.content.pm.PackageManager;
11import android.content.res.AssetManager;
12import android.os.AsyncTask;
13import android.os.Build;
14import androidx.annotation.NonNull;
15import androidx.annotation.WorkerThread;
16import io.flutter.BuildConfig;
17import io.flutter.Log;
18import java.io.*;
19import java.util.Collection;
20import java.util.HashSet;
21import java.util.concurrent.CancellationException;
22import java.util.concurrent.ExecutionException;
23
24/** A class to initialize the native code. */
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;
29
30 @SuppressWarnings("deprecation")
31 static long getVersionCode(@NonNull PackageInfo packageInfo) {
32 // Linter needs P (28) hardcoded or else it will fail these lines.
33 if (Build.VERSION.SDK_INT >= API_LEVELS.API_28) {
34 return packageInfo.getLongVersionCode();
35 } else {
36 return packageInfo.versionCode;
37 }
38 }
39
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;
46
47 ExtractTask(
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;
58 }
59
60 @Override
61 protected Void doInBackground(Void... unused) {
62 final File dataDir = new File(mDataDirPath);
63
64 final String timestamp = checkTimestamp(dataDir, mPackageManager, mPackageName);
65 if (timestamp == null) {
66 return null;
67 }
68
69 deleteFiles(mDataDirPath, mResources);
70
71 if (!extractAPK(dataDir)) {
72 return null;
73 }
74
75 if (timestamp != null) {
76 try {
77 new File(dataDir, timestamp).createNewFile();
78 } catch (IOException e) {
79 Log.w(TAG, "Failed to write resource timestamp");
80 }
81 }
82
83 return null;
84 }
85
86 /// Returns true if successfully unpacked APK resources,
87 /// otherwise deletes all resources and returns false.
88 @WorkerThread
89 private boolean extractAPK(@NonNull File dataDir) {
90 for (String asset : mResources) {
91 try {
92 final String resource = "assets/" + asset;
93 final File output = new File(dataDir, asset);
94 if (output.exists()) {
95 continue;
96 }
97 if (output.getParentFile() != null) {
98 output.getParentFile().mkdirs();
99 }
100
101 try (InputStream is = mAssetManager.open(asset);
102 OutputStream os = new FileOutputStream(output)) {
103 copy(is, os);
104 }
105 if (BuildConfig.DEBUG) {
106 Log.i(TAG, "Extracted baseline resource " + resource);
107 }
108 } catch (FileNotFoundException fnfe) {
109 continue;
110
111 } catch (IOException ioe) {
112 Log.w(TAG, "Exception unpacking resources: " + ioe.getMessage());
113 deleteFiles(mDataDirPath, mResources);
114 return false;
115 }
116 }
117
118 return true;
119 }
120 }
121
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;
128
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<>();
139 }
140
141 ResourceExtractor addResource(@NonNull String resource) {
142 mResources.add(resource);
143 return this;
144 }
145
146 ResourceExtractor addResources(@NonNull Collection<String> resources) {
147 mResources.addAll(resources);
148 return this;
149 }
150
152 if (BuildConfig.DEBUG && mExtractTask != null) {
153 Log.e(
154 TAG, "Attempted to start resource extraction while another extraction was in progress.");
155 }
156 mExtractTask =
157 new ExtractTask(mDataDirPath, mResources, mPackageName, mPackageManager, mAssetManager);
158 mExtractTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
159 return this;
160 }
161
163 if (mExtractTask == null) {
164 return;
165 }
166
167 try {
168 mExtractTask.get();
169 } catch (CancellationException | ExecutionException | InterruptedException e) {
170 deleteFiles(mDataDirPath, mResources);
171 }
172 }
173
174 private static String[] getExistingTimestamps(File dataDir) {
175 return dataDir.list(
176 new FilenameFilter() {
177 @Override
178 public boolean accept(File dir, String name) {
179 return name.startsWith(TIMESTAMP_PREFIX);
180 }
181 });
182 }
183
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);
188 if (file.exists()) {
189 file.delete();
190 }
191 }
192 final String[] existingTimestamps = getExistingTimestamps(dataDir);
193 if (existingTimestamps == null) {
194 return;
195 }
196 for (String timestamp : existingTimestamps) {
197 new File(dataDir, timestamp).delete();
198 }
199 }
200
201 // Returns null if extracted resources are found and match the current APK version
202 // and update version if any, otherwise returns the current APK and update version.
203 private static String checkTimestamp(
204 @NonNull File dataDir, @NonNull PackageManager packageManager, @NonNull String packageName) {
205 PackageInfo packageInfo = null;
206
207 try {
208 packageInfo = packageManager.getPackageInfo(packageName, 0);
209 } catch (PackageManager.NameNotFoundException e) {
210 return TIMESTAMP_PREFIX;
211 }
212
213 if (packageInfo == null) {
214 return TIMESTAMP_PREFIX;
215 }
216
217 String expectedTimestamp =
218 TIMESTAMP_PREFIX + getVersionCode(packageInfo) + "-" + packageInfo.lastUpdateTime;
219
220 final String[] existingTimestamps = getExistingTimestamps(dataDir);
221
222 if (existingTimestamps == null) {
223 if (BuildConfig.DEBUG) {
224 Log.i(TAG, "No extracted resources found");
225 }
226 return expectedTimestamp;
227 }
228
229 if (existingTimestamps.length == 1) {
230 if (BuildConfig.DEBUG) {
231 Log.i(TAG, "Found extracted resources " + existingTimestamps[0]);
232 }
233 }
234
235 if (existingTimestamps.length != 1 || !expectedTimestamp.equals(existingTimestamps[0])) {
236 if (BuildConfig.DEBUG) {
237 Log.i(TAG, "Resource version mismatch " + expectedTimestamp);
238 }
239 return expectedTimestamp;
240 }
241
242 return null;
243 }
244
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);
249 }
250 }
251}
static bool unused
static final boolean DEBUG
static void e(@NonNull String tag, @NonNull String message)
Definition Log.java:84
ResourceExtractor addResources(@NonNull Collection< String > resources)
ResourceExtractor addResource(@NonNull String resource)
static long getVersionCode(@NonNull PackageInfo packageInfo)
ResourceExtractor( @NonNull String dataDirPath, @NonNull String packageName, @NonNull PackageManager packageManager, @NonNull AssetManager assetManager)
const char * name
Definition fuchsia.cc:50
void Log(const char *format,...) SK_PRINTF_LIKE(1
Definition copy.py:1