Merge changes from topic "zip_and_sparse" am: f1d9e1ffc6

am: 2989d23283

Change-Id: Iefb6867f20734d6faa90f7b8eee66bb42a5ac15b
This commit is contained in:
Po-Chien Hsueh
2019-12-03 08:34:54 -08:00
committed by android-build-merger
3 changed files with 549 additions and 141 deletions

View File

@@ -32,9 +32,11 @@ import static android.os.image.DynamicSystemClient.STATUS_IN_USE;
import static android.os.image.DynamicSystemClient.STATUS_NOT_STARTED; import static android.os.image.DynamicSystemClient.STATUS_NOT_STARTED;
import static android.os.image.DynamicSystemClient.STATUS_READY; import static android.os.image.DynamicSystemClient.STATUS_READY;
import static com.android.dynsystem.InstallationAsyncTask.RESULT_CANCELLED;
import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_EXCEPTION; import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_EXCEPTION;
import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_INVALID_URL;
import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_IO; import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_IO;
import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_UNSUPPORTED_FORMAT;
import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_UNSUPPORTED_URL;
import static com.android.dynsystem.InstallationAsyncTask.RESULT_OK; import static com.android.dynsystem.InstallationAsyncTask.RESULT_OK;
import android.app.Notification; import android.app.Notification;
@@ -66,11 +68,10 @@ import java.util.ArrayList;
* cancel and confirm commnands. * cancel and confirm commnands.
*/ */
public class DynamicSystemInstallationService extends Service public class DynamicSystemInstallationService extends Service
implements InstallationAsyncTask.InstallStatusListener { implements InstallationAsyncTask.ProgressListener {
private static final String TAG = "DynSystemInstallationService"; private static final String TAG = "DynSystemInstallationService";
// TODO (b/131866826): This is currently for test only. Will move this to System API. // TODO (b/131866826): This is currently for test only. Will move this to System API.
static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED"; static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
@@ -121,9 +122,12 @@ public class DynamicSystemInstallationService extends Service
private DynamicSystemManager mDynSystem; private DynamicSystemManager mDynSystem;
private NotificationManager mNM; private NotificationManager mNM;
private long mSystemSize; private int mNumInstalledPartitions;
private long mUserdataSize;
private long mInstalledSize; private String mCurrentPartitionName;
private long mCurrentPartitionSize;
private long mCurrentPartitionInstalledSize;
private boolean mJustCancelledByUser; private boolean mJustCancelledByUser;
// This is for testing only now // This is for testing only now
@@ -176,8 +180,12 @@ public class DynamicSystemInstallationService extends Service
} }
@Override @Override
public void onProgressUpdate(long installedSize) { public void onProgressUpdate(InstallationAsyncTask.Progress progress) {
mInstalledSize = installedSize; mCurrentPartitionName = progress.mPartitionName;
mCurrentPartitionSize = progress.mPartitionSize;
mCurrentPartitionInstalledSize = progress.mInstalledSize;
mNumInstalledPartitions = progress.mNumInstalledPartitions;
postStatus(STATUS_IN_PROGRESS, CAUSE_NOT_SPECIFIED, null); postStatus(STATUS_IN_PROGRESS, CAUSE_NOT_SPECIFIED, null);
} }
@@ -197,11 +205,16 @@ public class DynamicSystemInstallationService extends Service
resetTaskAndStop(); resetTaskAndStop();
switch (result) { switch (result) {
case RESULT_CANCELLED:
postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED, null);
break;
case RESULT_ERROR_IO: case RESULT_ERROR_IO:
postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_IO, detail); postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_IO, detail);
break; break;
case RESULT_ERROR_INVALID_URL: case RESULT_ERROR_UNSUPPORTED_URL:
case RESULT_ERROR_UNSUPPORTED_FORMAT:
postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_INVALID_URL, detail); postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_INVALID_URL, detail);
break; break;
@@ -211,12 +224,6 @@ public class DynamicSystemInstallationService extends Service
} }
} }
@Override
public void onCancelled() {
resetTaskAndStop();
postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED, null);
}
private void executeInstallCommand(Intent intent) { private void executeInstallCommand(Intent intent) {
if (!verifyRequest(intent)) { if (!verifyRequest(intent)) {
Log.e(TAG, "Verification failed. Did you use VerificationActivity?"); Log.e(TAG, "Verification failed. Did you use VerificationActivity?");
@@ -234,12 +241,13 @@ public class DynamicSystemInstallationService extends Service
} }
String url = intent.getDataString(); String url = intent.getDataString();
mSystemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0); long systemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0);
mUserdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0); long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0);
mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false); mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false);
// TODO: better constructor or builder
mInstallTask = new InstallationAsyncTask( mInstallTask = new InstallationAsyncTask(
url, mSystemSize, mUserdataSize, this, mDynSystem, this); url, systemSize, userdataSize, this, mDynSystem, this);
mInstallTask.execute(); mInstallTask.execute();
@@ -257,7 +265,7 @@ public class DynamicSystemInstallationService extends Service
mJustCancelledByUser = true; mJustCancelledByUser = true;
if (mInstallTask.cancel(false)) { if (mInstallTask.cancel(false)) {
// Will cleanup and post status in onCancelled() // Will cleanup and post status in onResult()
Log.d(TAG, "Cancel request filed successfully"); Log.d(TAG, "Cancel request filed successfully");
} else { } else {
Log.e(TAG, "Trying to cancel installation while it's already completed."); Log.e(TAG, "Trying to cancel installation while it's already completed.");
@@ -288,7 +296,7 @@ public class DynamicSystemInstallationService extends Service
private void executeRebootToDynSystemCommand() { private void executeRebootToDynSystemCommand() {
boolean enabled = false; boolean enabled = false;
if (mInstallTask != null && mInstallTask.getResult() == RESULT_OK) { if (mInstallTask != null && mInstallTask.isCompleted()) {
enabled = mInstallTask.commit(); enabled = mInstallTask.commit();
} else if (isDynamicSystemInstalled()) { } else if (isDynamicSystemInstalled()) {
enabled = mDynSystem.setEnable(true, true); enabled = mDynSystem.setEnable(true, true);
@@ -380,8 +388,16 @@ public class DynamicSystemInstallationService extends Service
case STATUS_IN_PROGRESS: case STATUS_IN_PROGRESS:
builder.setContentText(getString(R.string.notification_install_inprogress)); builder.setContentText(getString(R.string.notification_install_inprogress));
int max = (int) Math.max((mSystemSize + mUserdataSize) >> 20, 1); int max = 1024;
int progress = (int) (mInstalledSize >> 20); int progress = 0;
int currentMax = max >> (mNumInstalledPartitions + 1);
progress = max - currentMax * 2;
long currentProgress = (mCurrentPartitionInstalledSize >> 20) * currentMax
/ Math.max(mCurrentPartitionSize >> 20, 1);
progress += (int) currentProgress;
builder.setProgress(max, progress, false); builder.setProgress(max, progress, false);
@@ -464,7 +480,8 @@ public class DynamicSystemInstallationService extends Service
throws RemoteException { throws RemoteException {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putLong(DynamicSystemClient.KEY_INSTALLED_SIZE, mInstalledSize); // TODO: send more info to the clients
bundle.putLong(DynamicSystemClient.KEY_INSTALLED_SIZE, mCurrentPartitionInstalledSize);
if (detail != null) { if (detail != null) {
bundle.putSerializable(DynamicSystemClient.KEY_EXCEPTION_DETAIL, bundle.putSerializable(DynamicSystemClient.KEY_EXCEPTION_DETAIL,
@@ -492,9 +509,7 @@ public class DynamicSystemInstallationService extends Service
return STATUS_IN_PROGRESS; return STATUS_IN_PROGRESS;
case FINISHED: case FINISHED:
int result = mInstallTask.getResult(); if (mInstallTask.isCompleted()) {
if (result == RESULT_OK) {
return STATUS_READY; return STATUS_READY;
} else { } else {
throw new IllegalStateException("A failed InstallationTask is not reset"); throw new IllegalStateException("A failed InstallationTask is not reset");

View File

@@ -17,7 +17,6 @@
package com.android.dynsystem; package com.android.dynsystem;
import android.content.Context; import android.content.Context;
import android.gsi.GsiProgress;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.MemoryFile; import android.os.MemoryFile;
@@ -27,35 +26,70 @@ import android.util.Log;
import android.webkit.URLUtil; import android.webkit.URLUtil;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Progress, Throwable> {
private static final String TAG = "InstallationAsyncTask"; private static final String TAG = "InstallationAsyncTask";
private static final int READ_BUFFER_SIZE = 1 << 13; private static final int READ_BUFFER_SIZE = 1 << 13;
private static final long MIN_PROGRESS_TO_PUBLISH = 1 << 27;
private class InvalidImageUrlException extends RuntimeException { private static final List<String> UNSUPPORTED_PARTITIONS =
private InvalidImageUrlException(String message) { Arrays.asList("vbmeta", "boot", "userdata", "dtbo", "super_empty", "system_other");
private class UnsupportedUrlException extends RuntimeException {
private UnsupportedUrlException(String message) {
super(message); super(message);
} }
} }
/** Not completed, including being cancelled */ private class UnsupportedFormatException extends RuntimeException {
static final int NO_RESULT = 0; private UnsupportedFormatException(String message) {
super(message);
}
}
/** UNSET means the installation is not completed */
static final int RESULT_UNSET = 0;
static final int RESULT_OK = 1; static final int RESULT_OK = 1;
static final int RESULT_ERROR_IO = 2; static final int RESULT_CANCELLED = 2;
static final int RESULT_ERROR_INVALID_URL = 3; static final int RESULT_ERROR_IO = 3;
static final int RESULT_ERROR_UNSUPPORTED_URL = 4;
static final int RESULT_ERROR_UNSUPPORTED_FORMAT = 5;
static final int RESULT_ERROR_EXCEPTION = 6; static final int RESULT_ERROR_EXCEPTION = 6;
interface InstallStatusListener { class Progress {
void onProgressUpdate(long installedSize); String mPartitionName;
long mPartitionSize;
long mInstalledSize;
int mNumInstalledPartitions;
Progress(String partitionName, long partitionSize, long installedSize,
int numInstalled) {
mPartitionName = partitionName;
mPartitionSize = partitionSize;
mInstalledSize = installedSize;
mNumInstalledPartitions = numInstalled;
}
}
interface ProgressListener {
void onProgressUpdate(Progress progress);
void onResult(int resultCode, Throwable detail); void onResult(int resultCode, Throwable detail);
void onCancelled();
} }
private final String mUrl; private final String mUrl;
@@ -63,16 +97,17 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> {
private final long mUserdataSize; private final long mUserdataSize;
private final Context mContext; private final Context mContext;
private final DynamicSystemManager mDynSystem; private final DynamicSystemManager mDynSystem;
private final InstallStatusListener mListener; private final ProgressListener mListener;
private DynamicSystemManager.Session mInstallationSession; private DynamicSystemManager.Session mInstallationSession;
private int mResult = NO_RESULT; private boolean mIsZip;
private boolean mIsCompleted;
private InputStream mStream; private InputStream mStream;
private ZipFile mZipFile;
InstallationAsyncTask(String url, long systemSize, long userdataSize, Context context, InstallationAsyncTask(String url, long systemSize, long userdataSize, Context context,
DynamicSystemManager dynSystem, InstallStatusListener listener) { DynamicSystemManager dynSystem, ProgressListener listener) {
mUrl = url; mUrl = url;
mSystemSize = systemSize; mSystemSize = systemSize;
mUserdataSize = userdataSize; mUserdataSize = userdataSize;
@@ -81,134 +116,293 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> {
mListener = listener; mListener = listener;
} }
@Override
protected void onPreExecute() {
mListener.onProgressUpdate(0);
}
@Override @Override
protected Throwable doInBackground(String... voids) { protected Throwable doInBackground(String... voids) {
Log.d(TAG, "Start doInBackground(), URL: " + mUrl); Log.d(TAG, "Start doInBackground(), URL: " + mUrl);
try { try {
long installedSize = 0; // call DynamicSystemManager to cleanup stuff
long reportedInstalledSize = 0; mDynSystem.remove();
long minStepToReport = (mSystemSize + mUserdataSize) / 100; verifyAndPrepare();
// init input stream before calling startInstallation(), which takes 90 seconds. mDynSystem.startInstallation();
initInputStream();
Thread thread = installUserdata();
new Thread( if (isCancelled()) {
() -> { mDynSystem.remove();
mDynSystem.startInstallation(); return null;
mDynSystem.createPartition("userdata", mUserdataSize, false);
mInstallationSession =
mDynSystem.createPartition("system", mSystemSize, true);
});
thread.start();
while (thread.isAlive()) {
if (isCancelled()) {
boolean aborted = mDynSystem.abort();
Log.d(TAG, "Called DynamicSystemManager.abort(), result = " + aborted);
return null;
}
GsiProgress progress = mDynSystem.getInstallationProgress();
installedSize = progress.bytes_processed;
if (installedSize > reportedInstalledSize + minStepToReport) {
publishProgress(installedSize);
reportedInstalledSize = installedSize;
}
Thread.sleep(10);
} }
if (mInstallationSession == null) { installImages();
throw new IOException( if (isCancelled()) {
"Failed to start installation with requested size: " mDynSystem.remove();
+ (mSystemSize + mUserdataSize)); return null;
} }
installedSize = mUserdataSize;
MemoryFile memoryFile = new MemoryFile("dsu", READ_BUFFER_SIZE);
byte[] bytes = new byte[READ_BUFFER_SIZE];
mInstallationSession.setAshmem(
new ParcelFileDescriptor(memoryFile.getFileDescriptor()), READ_BUFFER_SIZE);
int numBytesRead;
Log.d(TAG, "Start installation loop");
while ((numBytesRead = mStream.read(bytes, 0, READ_BUFFER_SIZE)) != -1) {
memoryFile.writeBytes(bytes, 0, 0, numBytesRead);
if (isCancelled()) {
break;
}
if (!mInstallationSession.submitFromAshmem(numBytesRead)) {
throw new IOException("Failed write() to DynamicSystem");
}
installedSize += numBytesRead;
if (installedSize > reportedInstalledSize + minStepToReport) {
publishProgress(installedSize);
reportedInstalledSize = installedSize;
}
}
mDynSystem.finishInstallation(); mDynSystem.finishInstallation();
return null;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
mDynSystem.remove();
return e; return e;
} finally { } finally {
close(); close();
} }
return null;
}
@Override
protected void onPostExecute(Throwable detail) {
int result = RESULT_UNSET;
if (detail == null) {
result = RESULT_OK;
mIsCompleted = true;
} else if (detail instanceof IOException) {
result = RESULT_ERROR_IO;
} else if (detail instanceof UnsupportedUrlException) {
result = RESULT_ERROR_UNSUPPORTED_URL;
} else if (detail instanceof UnsupportedFormatException) {
result = RESULT_ERROR_UNSUPPORTED_FORMAT;
} else {
result = RESULT_ERROR_EXCEPTION;
}
Log.d(TAG, "onPostExecute(), URL: " + mUrl + ", result: " + result);
mListener.onResult(result, detail);
} }
@Override @Override
protected void onCancelled() { protected void onCancelled() {
Log.d(TAG, "onCancelled(), URL: " + mUrl); Log.d(TAG, "onCancelled(), URL: " + mUrl);
mListener.onCancelled(); if (mDynSystem.abort()) {
} Log.d(TAG, "Installation aborted");
@Override
protected void onPostExecute(Throwable detail) {
if (detail == null) {
mResult = RESULT_OK;
} else if (detail instanceof IOException) {
mResult = RESULT_ERROR_IO;
} else if (detail instanceof InvalidImageUrlException) {
mResult = RESULT_ERROR_INVALID_URL;
} else { } else {
mResult = RESULT_ERROR_EXCEPTION; Log.w(TAG, "DynamicSystemManager.abort() returned false");
} }
Log.d(TAG, "onPostExecute(), URL: " + mUrl + ", result: " + mResult); mListener.onResult(RESULT_CANCELLED, null);
mListener.onResult(mResult, detail);
} }
@Override @Override
protected void onProgressUpdate(Long... values) { protected void onProgressUpdate(Progress... values) {
long progress = values[0]; Progress progress = values[0];
mListener.onProgressUpdate(progress); mListener.onProgressUpdate(progress);
} }
private void initInputStream() throws IOException, InvalidImageUrlException { private void verifyAndPrepare() throws Exception {
if (URLUtil.isNetworkUrl(mUrl) || URLUtil.isFileUrl(mUrl)) { String extension = mUrl.substring(mUrl.lastIndexOf('.') + 1);
mStream = new BufferedInputStream(new GZIPInputStream(new URL(mUrl).openStream()));
} else if (URLUtil.isContentUrl(mUrl)) { if ("gz".equals(extension) || "gzip".equals(extension)) {
Uri uri = Uri.parse(mUrl); mIsZip = false;
mStream = new BufferedInputStream(new GZIPInputStream( } else if ("zip".equals(extension)) {
mContext.getContentResolver().openInputStream(uri))); mIsZip = true;
} else { } else {
throw new InvalidImageUrlException( throw new UnsupportedFormatException(
String.format(Locale.US, "Unsupported file source: %s", mUrl)); String.format(Locale.US, "Unsupported file format: %s", mUrl));
}
if (URLUtil.isNetworkUrl(mUrl)) {
mStream = new URL(mUrl).openStream();
} else if (URLUtil.isFileUrl(mUrl)) {
if (mIsZip) {
mZipFile = new ZipFile(new File(new URL(mUrl).toURI()));
} else {
mStream = new URL(mUrl).openStream();
}
} else if (URLUtil.isContentUrl(mUrl)) {
mStream = mContext.getContentResolver().openInputStream(Uri.parse(mUrl));
} else {
throw new UnsupportedUrlException(
String.format(Locale.US, "Unsupported URL: %s", mUrl));
}
}
private void installUserdata() throws Exception {
Thread thread = new Thread(() -> {
mInstallationSession = mDynSystem.createPartition("userdata", mUserdataSize, false);
});
Log.d(TAG, "Creating partition: userdata");
thread.start();
long installedSize = 0;
Progress progress = new Progress("userdata", mUserdataSize, installedSize, 0);
while (thread.isAlive()) {
if (isCancelled()) {
return;
}
installedSize = mDynSystem.getInstallationProgress().bytes_processed;
if (installedSize > progress.mInstalledSize + MIN_PROGRESS_TO_PUBLISH) {
progress.mInstalledSize = installedSize;
publishProgress(progress);
}
Thread.sleep(10);
}
if (mInstallationSession == null) {
throw new IOException(
"Failed to start installation with requested size: " + mUserdataSize);
}
}
private void installImages() throws IOException, InterruptedException {
if (mStream != null) {
if (mIsZip) {
installStreamingZipUpdate();
} else {
installStreamingGzUpdate();
}
} else {
installLocalZipUpdate();
}
}
private void installStreamingGzUpdate() throws IOException, InterruptedException {
Log.d(TAG, "To install a streaming GZ update");
installImage("system", mSystemSize, new GZIPInputStream(mStream), 1);
}
private void installStreamingZipUpdate() throws IOException, InterruptedException {
Log.d(TAG, "To install a streaming ZIP update");
ZipInputStream zis = new ZipInputStream(mStream);
ZipEntry zipEntry = null;
int numInstalledPartitions = 1;
while ((zipEntry = zis.getNextEntry()) != null) {
if (installImageFromAnEntry(zipEntry, zis, numInstalledPartitions)) {
numInstalledPartitions++;
}
if (isCancelled()) {
break;
}
}
}
private void installLocalZipUpdate() throws IOException, InterruptedException {
Log.d(TAG, "To install a local ZIP update");
Enumeration<? extends ZipEntry> entries = mZipFile.entries();
int numInstalledPartitions = 1;
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (installImageFromAnEntry(
entry, mZipFile.getInputStream(entry), numInstalledPartitions)) {
numInstalledPartitions++;
}
if (isCancelled()) {
break;
}
}
}
private boolean installImageFromAnEntry(ZipEntry entry, InputStream is,
int numInstalledPartitions) throws IOException, InterruptedException {
String name = entry.getName();
Log.d(TAG, "ZipEntry: " + name);
if (!name.endsWith(".img")) {
return false;
}
String partitionName = name.substring(0, name.length() - 4);
if (UNSUPPORTED_PARTITIONS.contains(partitionName)) {
Log.d(TAG, name + " installation is not supported, skip it.");
return false;
}
long uncompressedSize = entry.getSize();
installImage(partitionName, uncompressedSize, is, numInstalledPartitions);
return true;
}
private void installImage(String partitionName, long uncompressedSize, InputStream is,
int numInstalledPartitions) throws IOException, InterruptedException {
SparseInputStream sis = new SparseInputStream(new BufferedInputStream(is));
long unsparseSize = sis.getUnsparseSize();
final long partitionSize;
if (unsparseSize != -1) {
partitionSize = unsparseSize;
Log.d(TAG, partitionName + " is sparse, raw size = " + unsparseSize);
} else if (uncompressedSize != -1) {
partitionSize = uncompressedSize;
Log.d(TAG, partitionName + " is already unsparse, raw size = " + uncompressedSize);
} else {
throw new IOException("Cannot get raw size for " + partitionName);
}
Thread thread = new Thread(() -> {
mInstallationSession =
mDynSystem.createPartition(partitionName, partitionSize, true);
});
Log.d(TAG, "Start creating partition: " + partitionName);
thread.start();
while (thread.isAlive()) {
if (isCancelled()) {
return;
}
Thread.sleep(10);
}
if (mInstallationSession == null) {
throw new IOException(
"Failed to start installation with requested size: " + partitionSize);
}
Log.d(TAG, "Start installing: " + partitionName);
MemoryFile memoryFile = new MemoryFile("dsu_" + partitionName, READ_BUFFER_SIZE);
ParcelFileDescriptor pfd = new ParcelFileDescriptor(memoryFile.getFileDescriptor());
mInstallationSession.setAshmem(pfd, READ_BUFFER_SIZE);
long installedSize = 0;
Progress progress = new Progress(
partitionName, partitionSize, installedSize, numInstalledPartitions);
byte[] bytes = new byte[READ_BUFFER_SIZE];
int numBytesRead;
while ((numBytesRead = sis.read(bytes, 0, READ_BUFFER_SIZE)) != -1) {
if (isCancelled()) {
return;
}
memoryFile.writeBytes(bytes, 0, 0, numBytesRead);
if (!mInstallationSession.submitFromAshmem(numBytesRead)) {
throw new IOException("Failed write() to DynamicSystem");
}
installedSize += numBytesRead;
if (installedSize > progress.mInstalledSize + MIN_PROGRESS_TO_PUBLISH) {
progress.mInstalledSize = installedSize;
publishProgress(progress);
}
} }
} }
@@ -218,20 +412,20 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> {
mStream.close(); mStream.close();
mStream = null; mStream = null;
} }
if (mZipFile != null) {
mZipFile.close();
mZipFile = null;
}
} catch (IOException e) { } catch (IOException e) {
// ignore // ignore
} }
} }
int getResult() { boolean isCompleted() {
return mResult; return mIsCompleted;
} }
boolean commit() { boolean commit() {
if (mInstallationSession == null) { return mDynSystem.setEnable(true, true);
return false;
}
return mInstallationSession.commit();
} }
} }

View File

@@ -0,0 +1,199 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.dynsystem;
import static java.lang.Math.min;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
/**
* SparseInputStream read from upstream and detects the data format. If the upstream is a valid
* sparse data, it will unsparse it on the fly. Otherwise, it just passthrough as is.
*/
public class SparseInputStream extends InputStream {
static final int FILE_HDR_SIZE = 28;
static final int CHUNK_HDR_SIZE = 12;
/**
* This class represents a chunk in the Android sparse image.
*
* @see system/core/libsparse/sparse_format.h
*/
private class SparseChunk {
static final short RAW = (short) 0xCAC1;
static final short FILL = (short) 0xCAC2;
static final short DONTCARE = (short) 0xCAC3;
public short mChunkType;
public int mChunkSize;
public int mTotalSize;
public byte[] fill;
public String toString() {
return String.format(
"type: %x, chunk_size: %d, total_size: %d", mChunkType, mChunkSize, mTotalSize);
}
}
private byte[] readFull(InputStream in, int size) throws IOException {
byte[] buf = new byte[size];
for (int done = 0, n = 0; done < size; done += n) {
if ((n = in.read(buf, done, size - done)) < 0) {
throw new IOException("Failed to readFull");
}
}
return buf;
}
private ByteBuffer readBuffer(InputStream in, int size) throws IOException {
return ByteBuffer.wrap(readFull(in, size)).order(ByteOrder.LITTLE_ENDIAN);
}
private SparseChunk readChunk(InputStream in) throws IOException {
SparseChunk chunk = new SparseChunk();
ByteBuffer buf = readBuffer(in, CHUNK_HDR_SIZE);
chunk.mChunkType = buf.getShort();
buf.getShort();
chunk.mChunkSize = buf.getInt();
chunk.mTotalSize = buf.getInt();
return chunk;
}
private BufferedInputStream mIn;
private boolean mIsSparse;
private long mBlockSize;
private long mTotalBlocks;
private long mTotalChunks;
private SparseChunk mCur;
private long mLeft;
private int mCurChunks;
public SparseInputStream(BufferedInputStream in) throws IOException {
mIn = in;
in.mark(FILE_HDR_SIZE * 2);
ByteBuffer buf = readBuffer(mIn, FILE_HDR_SIZE);
mIsSparse = (buf.getInt() == 0xed26ff3a);
if (!mIsSparse) {
mIn.reset();
return;
}
int major = buf.getShort();
int minor = buf.getShort();
if (major > 0x1 || minor > 0x0) {
throw new IOException("Unsupported sparse version: " + major + "." + minor);
}
if (buf.getShort() != FILE_HDR_SIZE) {
throw new IOException("Illegal file header size");
}
if (buf.getShort() != CHUNK_HDR_SIZE) {
throw new IOException("Illegal chunk header size");
}
mBlockSize = buf.getInt();
if ((mBlockSize & 0x3) != 0) {
throw new IOException("Illegal block size, must be a multiple of 4");
}
mTotalBlocks = buf.getInt();
mTotalChunks = buf.getInt();
mLeft = mCurChunks = 0;
}
/**
* Check if it needs to open a new chunk.
*
* @return true if it's EOF
*/
private boolean prepareChunk() throws IOException {
if (mCur == null || mLeft <= 0) {
if (++mCurChunks > mTotalChunks) return true;
mCur = readChunk(mIn);
if (mCur.mChunkType == SparseChunk.FILL) {
mCur.fill = readFull(mIn, 4);
}
mLeft = mCur.mChunkSize * mBlockSize;
}
return mLeft == 0;
}
/**
* It overrides the InputStream.read(byte[] buf)
*/
public int read(byte[] buf) throws IOException {
if (!mIsSparse) {
return mIn.read(buf);
}
if (prepareChunk()) return -1;
int n = -1;
switch (mCur.mChunkType) {
case SparseChunk.RAW:
n = mIn.read(buf, 0, (int) min(mLeft, buf.length));
mLeft -= n;
return n;
case SparseChunk.DONTCARE:
n = (int) min(mLeft, buf.length);
Arrays.fill(buf, 0, n - 1, (byte) 0);
mLeft -= n;
return n;
case SparseChunk.FILL:
// The FILL type is rarely used, so use a simple implmentation.
return super.read(buf);
default:
throw new IOException("Unsupported Chunk:" + mCur.toString());
}
}
/**
* It overrides the InputStream.read()
*/
public int read() throws IOException {
if (!mIsSparse) {
return mIn.read();
}
if (prepareChunk()) return -1;
int ret = -1;
switch (mCur.mChunkType) {
case SparseChunk.RAW:
ret = mIn.read();
break;
case SparseChunk.DONTCARE:
ret = 0;
break;
case SparseChunk.FILL:
ret = mCur.fill[(4 - ((int) mLeft & 0x3)) & 0x3];
break;
default:
throw new IOException("Unsupported Chunk:" + mCur.toString());
}
mLeft--;
return ret;
}
/**
* Get the unsparse size
* @return -1 if unknown
*/
public long getUnsparseSize() {
if (!mIsSparse) {
return -1;
}
return mBlockSize * mTotalBlocks;
}
}