blob: b206a1fccba40768fc89513eac62c2771414a714 [file] [log] [blame]
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +08001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Po-Chien Hsueh4e908c22019-03-07 11:57:17 +080017package com.android.dynsystem;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080018
Po-Chien Hsuehc51cf0f2019-03-21 17:16:06 +080019import android.content.Context;
Po-Chien Hsuehc51cf0f2019-03-21 17:16:06 +080020import android.net.Uri;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080021import android.os.AsyncTask;
Howard Chenae615b32019-08-05 16:56:12 +080022import android.os.MemoryFile;
23import android.os.ParcelFileDescriptor;
Po-Chien Hsueh4e908c22019-03-07 11:57:17 +080024import android.os.image.DynamicSystemManager;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080025import android.util.Log;
26import android.webkit.URLUtil;
27
28import java.io.BufferedInputStream;
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +080029import java.io.File;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080030import java.io.IOException;
31import java.io.InputStream;
32import java.net.URL;
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +080033import java.util.Arrays;
34import java.util.Enumeration;
35import java.util.List;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080036import java.util.Locale;
37import java.util.zip.GZIPInputStream;
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +080038import java.util.zip.ZipEntry;
39import java.util.zip.ZipFile;
40import java.util.zip.ZipInputStream;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080041
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +080042class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Progress, Throwable> {
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080043
44 private static final String TAG = "InstallationAsyncTask";
45
Po-Chien Hsueh22c1f9a2019-05-17 15:08:20 +080046 private static final int READ_BUFFER_SIZE = 1 << 13;
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +080047 private static final long MIN_PROGRESS_TO_PUBLISH = 1 << 27;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080048
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +080049 private static final List<String> UNSUPPORTED_PARTITIONS =
50 Arrays.asList("vbmeta", "boot", "userdata", "dtbo", "super_empty", "system_other");
51
52 private class UnsupportedUrlException extends RuntimeException {
53 private UnsupportedUrlException(String message) {
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080054 super(message);
55 }
56 }
57
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +080058 private class UnsupportedFormatException extends RuntimeException {
59 private UnsupportedFormatException(String message) {
60 super(message);
61 }
62 }
63
64 /** UNSET means the installation is not completed */
65 static final int RESULT_UNSET = 0;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080066 static final int RESULT_OK = 1;
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +080067 static final int RESULT_CANCELLED = 2;
68 static final int RESULT_ERROR_IO = 3;
69 static final int RESULT_ERROR_UNSUPPORTED_URL = 4;
70 static final int RESULT_ERROR_UNSUPPORTED_FORMAT = 5;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080071 static final int RESULT_ERROR_EXCEPTION = 6;
72
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +080073 class Progress {
74 String mPartitionName;
75 long mPartitionSize;
76 long mInstalledSize;
77
78 int mNumInstalledPartitions;
79
80 Progress(String partitionName, long partitionSize, long installedSize,
81 int numInstalled) {
82 mPartitionName = partitionName;
83 mPartitionSize = partitionSize;
84 mInstalledSize = installedSize;
85
86 mNumInstalledPartitions = numInstalled;
87 }
88 }
89
90 interface ProgressListener {
91 void onProgressUpdate(Progress progress);
Po-Chien Hsueha5bd0842019-03-19 12:56:19 +080092 void onResult(int resultCode, Throwable detail);
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080093 }
94
95 private final String mUrl;
96 private final long mSystemSize;
97 private final long mUserdataSize;
Po-Chien Hsuehc51cf0f2019-03-21 17:16:06 +080098 private final Context mContext;
Po-Chien Hsueh4e908c22019-03-07 11:57:17 +080099 private final DynamicSystemManager mDynSystem;
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800100 private final ProgressListener mListener;
Po-Chien Hsueh4e908c22019-03-07 11:57:17 +0800101 private DynamicSystemManager.Session mInstallationSession;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800102
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800103 private boolean mIsZip;
104 private boolean mIsCompleted;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800105
106 private InputStream mStream;
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800107 private ZipFile mZipFile;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800108
Po-Chien Hsuehc51cf0f2019-03-21 17:16:06 +0800109 InstallationAsyncTask(String url, long systemSize, long userdataSize, Context context,
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800110 DynamicSystemManager dynSystem, ProgressListener listener) {
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800111 mUrl = url;
112 mSystemSize = systemSize;
113 mUserdataSize = userdataSize;
Po-Chien Hsuehc51cf0f2019-03-21 17:16:06 +0800114 mContext = context;
Po-Chien Hsueh4e908c22019-03-07 11:57:17 +0800115 mDynSystem = dynSystem;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800116 mListener = listener;
117 }
118
119 @Override
Po-Chien Hsueha5bd0842019-03-19 12:56:19 +0800120 protected Throwable doInBackground(String... voids) {
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800121 Log.d(TAG, "Start doInBackground(), URL: " + mUrl);
122
123 try {
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800124 // call DynamicSystemManager to cleanup stuff
125 mDynSystem.remove();
Po-Chien Hsueh9c1500f2019-03-04 14:47:46 +0800126
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800127 verifyAndPrepare();
Po-Chien Hsueh9c1500f2019-03-04 14:47:46 +0800128
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800129 mDynSystem.startInstallation();
Po-Chien Hsueh9c1500f2019-03-04 14:47:46 +0800130
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800131 installUserdata();
132 if (isCancelled()) {
133 mDynSystem.remove();
134 return null;
Po-Chien Hsueh9c1500f2019-03-04 14:47:46 +0800135 }
136
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800137 installImages();
138 if (isCancelled()) {
139 mDynSystem.remove();
140 return null;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800141 }
142
Howard Chen6ea5bed2019-10-04 18:15:14 +0800143 mDynSystem.finishInstallation();
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800144 } catch (Exception e) {
145 e.printStackTrace();
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800146 mDynSystem.remove();
Po-Chien Hsueha5bd0842019-03-19 12:56:19 +0800147 return e;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800148 } finally {
149 close();
150 }
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800151
152 return null;
153 }
154
155 @Override
156 protected void onPostExecute(Throwable detail) {
157 int result = RESULT_UNSET;
158
159 if (detail == null) {
160 result = RESULT_OK;
161 mIsCompleted = true;
162 } else if (detail instanceof IOException) {
163 result = RESULT_ERROR_IO;
164 } else if (detail instanceof UnsupportedUrlException) {
165 result = RESULT_ERROR_UNSUPPORTED_URL;
166 } else if (detail instanceof UnsupportedFormatException) {
167 result = RESULT_ERROR_UNSUPPORTED_FORMAT;
168 } else {
169 result = RESULT_ERROR_EXCEPTION;
170 }
171
172 Log.d(TAG, "onPostExecute(), URL: " + mUrl + ", result: " + result);
173
174 mListener.onResult(result, detail);
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800175 }
176
177 @Override
178 protected void onCancelled() {
179 Log.d(TAG, "onCancelled(), URL: " + mUrl);
180
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800181 if (mDynSystem.abort()) {
182 Log.d(TAG, "Installation aborted");
Po-Chien Hsueha5bd0842019-03-19 12:56:19 +0800183 } else {
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800184 Log.w(TAG, "DynamicSystemManager.abort() returned false");
Po-Chien Hsueha5bd0842019-03-19 12:56:19 +0800185 }
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800186
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800187 mListener.onResult(RESULT_CANCELLED, null);
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800188 }
189
190 @Override
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800191 protected void onProgressUpdate(Progress... values) {
192 Progress progress = values[0];
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800193 mListener.onProgressUpdate(progress);
194 }
195
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800196 private void verifyAndPrepare() throws Exception {
197 String extension = mUrl.substring(mUrl.lastIndexOf('.') + 1);
198
199 if ("gz".equals(extension) || "gzip".equals(extension)) {
200 mIsZip = false;
201 } else if ("zip".equals(extension)) {
202 mIsZip = true;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800203 } else {
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800204 throw new UnsupportedFormatException(
205 String.format(Locale.US, "Unsupported file format: %s", mUrl));
206 }
207
208 if (URLUtil.isNetworkUrl(mUrl)) {
209 mStream = new URL(mUrl).openStream();
210 } else if (URLUtil.isFileUrl(mUrl)) {
211 if (mIsZip) {
212 mZipFile = new ZipFile(new File(new URL(mUrl).toURI()));
213 } else {
214 mStream = new URL(mUrl).openStream();
215 }
216 } else if (URLUtil.isContentUrl(mUrl)) {
217 mStream = mContext.getContentResolver().openInputStream(Uri.parse(mUrl));
218 } else {
219 throw new UnsupportedUrlException(
220 String.format(Locale.US, "Unsupported URL: %s", mUrl));
221 }
222 }
223
224 private void installUserdata() throws Exception {
225 Thread thread = new Thread(() -> {
226 mInstallationSession = mDynSystem.createPartition("userdata", mUserdataSize, false);
227 });
228
229 Log.d(TAG, "Creating partition: userdata");
230 thread.start();
231
232 long installedSize = 0;
233 Progress progress = new Progress("userdata", mUserdataSize, installedSize, 0);
234
235 while (thread.isAlive()) {
236 if (isCancelled()) {
237 return;
238 }
239
240 installedSize = mDynSystem.getInstallationProgress().bytes_processed;
241
242 if (installedSize > progress.mInstalledSize + MIN_PROGRESS_TO_PUBLISH) {
243 progress.mInstalledSize = installedSize;
244 publishProgress(progress);
245 }
246
247 Thread.sleep(10);
248 }
249
250 if (mInstallationSession == null) {
251 throw new IOException(
252 "Failed to start installation with requested size: " + mUserdataSize);
253 }
254 }
255
256 private void installImages() throws IOException, InterruptedException {
257 if (mStream != null) {
258 if (mIsZip) {
259 installStreamingZipUpdate();
260 } else {
261 installStreamingGzUpdate();
262 }
263 } else {
264 installLocalZipUpdate();
265 }
266 }
267
268 private void installStreamingGzUpdate() throws IOException, InterruptedException {
269 Log.d(TAG, "To install a streaming GZ update");
270 installImage("system", mSystemSize, new GZIPInputStream(mStream), 1);
271 }
272
273 private void installStreamingZipUpdate() throws IOException, InterruptedException {
274 Log.d(TAG, "To install a streaming ZIP update");
275
276 ZipInputStream zis = new ZipInputStream(mStream);
277 ZipEntry zipEntry = null;
278
279 int numInstalledPartitions = 1;
280
281 while ((zipEntry = zis.getNextEntry()) != null) {
282 if (installImageFromAnEntry(zipEntry, zis, numInstalledPartitions)) {
283 numInstalledPartitions++;
284 }
285
286 if (isCancelled()) {
287 break;
288 }
289 }
290 }
291
292 private void installLocalZipUpdate() throws IOException, InterruptedException {
293 Log.d(TAG, "To install a local ZIP update");
294
295 Enumeration<? extends ZipEntry> entries = mZipFile.entries();
296 int numInstalledPartitions = 1;
297
298 while (entries.hasMoreElements()) {
299 ZipEntry entry = entries.nextElement();
300 if (installImageFromAnEntry(
301 entry, mZipFile.getInputStream(entry), numInstalledPartitions)) {
302 numInstalledPartitions++;
303 }
304
305 if (isCancelled()) {
306 break;
307 }
308 }
309 }
310
311 private boolean installImageFromAnEntry(ZipEntry entry, InputStream is,
312 int numInstalledPartitions) throws IOException, InterruptedException {
313 String name = entry.getName();
314
315 Log.d(TAG, "ZipEntry: " + name);
316
317 if (!name.endsWith(".img")) {
318 return false;
319 }
320
321 String partitionName = name.substring(0, name.length() - 4);
322
323 if (UNSUPPORTED_PARTITIONS.contains(partitionName)) {
324 Log.d(TAG, name + " installation is not supported, skip it.");
325 return false;
326 }
327
328 long uncompressedSize = entry.getSize();
329
330 installImage(partitionName, uncompressedSize, is, numInstalledPartitions);
331
332 return true;
333 }
334
335 private void installImage(String partitionName, long uncompressedSize, InputStream is,
336 int numInstalledPartitions) throws IOException, InterruptedException {
337
338 SparseInputStream sis = new SparseInputStream(new BufferedInputStream(is));
339
340 long unsparseSize = sis.getUnsparseSize();
341
342 final long partitionSize;
343
344 if (unsparseSize != -1) {
345 partitionSize = unsparseSize;
346 Log.d(TAG, partitionName + " is sparse, raw size = " + unsparseSize);
347 } else if (uncompressedSize != -1) {
348 partitionSize = uncompressedSize;
349 Log.d(TAG, partitionName + " is already unsparse, raw size = " + uncompressedSize);
350 } else {
351 throw new IOException("Cannot get raw size for " + partitionName);
352 }
353
354 Thread thread = new Thread(() -> {
355 mInstallationSession =
356 mDynSystem.createPartition(partitionName, partitionSize, true);
357 });
358
359 Log.d(TAG, "Start creating partition: " + partitionName);
360 thread.start();
361
362 while (thread.isAlive()) {
363 if (isCancelled()) {
364 return;
365 }
366
367 Thread.sleep(10);
368 }
369
370 if (mInstallationSession == null) {
371 throw new IOException(
372 "Failed to start installation with requested size: " + partitionSize);
373 }
374
375 Log.d(TAG, "Start installing: " + partitionName);
376
377 MemoryFile memoryFile = new MemoryFile("dsu_" + partitionName, READ_BUFFER_SIZE);
378 ParcelFileDescriptor pfd = new ParcelFileDescriptor(memoryFile.getFileDescriptor());
379
380 mInstallationSession.setAshmem(pfd, READ_BUFFER_SIZE);
381
382 long installedSize = 0;
383 Progress progress = new Progress(
384 partitionName, partitionSize, installedSize, numInstalledPartitions);
385
386 byte[] bytes = new byte[READ_BUFFER_SIZE];
387 int numBytesRead;
388
389 while ((numBytesRead = sis.read(bytes, 0, READ_BUFFER_SIZE)) != -1) {
390 if (isCancelled()) {
391 return;
392 }
393
394 memoryFile.writeBytes(bytes, 0, 0, numBytesRead);
395
396 if (!mInstallationSession.submitFromAshmem(numBytesRead)) {
397 throw new IOException("Failed write() to DynamicSystem");
398 }
399
400 installedSize += numBytesRead;
401
402 if (installedSize > progress.mInstalledSize + MIN_PROGRESS_TO_PUBLISH) {
403 progress.mInstalledSize = installedSize;
404 publishProgress(progress);
405 }
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800406 }
407 }
408
409 private void close() {
410 try {
411 if (mStream != null) {
412 mStream.close();
413 mStream = null;
414 }
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800415 if (mZipFile != null) {
416 mZipFile.close();
417 mZipFile = null;
418 }
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800419 } catch (IOException e) {
420 // ignore
421 }
422 }
423
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800424 boolean isCompleted() {
425 return mIsCompleted;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800426 }
427
428 boolean commit() {
Po-Chien Hsueh99d18dd2019-10-30 16:48:15 +0800429 return mDynSystem.setEnable(true, true);
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800430 }
431}