Merge changes from topic "zip_and_sparse" am: f1d9e1ffc6
am: 2989d23283
Change-Id: Iefb6867f20734d6faa90f7b8eee66bb42a5ac15b
This commit is contained in:
@@ -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");
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user