diff options
Diffstat (limited to 'packages/DynamicSystemInstallationService/src')
2 files changed, 347 insertions, 138 deletions
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index 142078e1b77c..9e49826f70c3 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -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_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_INVALID_URL; 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 android.app.Notification; @@ -66,11 +68,10 @@ import java.util.ArrayList; * cancel and confirm commnands. */ public class DynamicSystemInstallationService extends Service - implements InstallationAsyncTask.InstallStatusListener { + implements InstallationAsyncTask.ProgressListener { private static final String TAG = "DynSystemInstallationService"; - // 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"; @@ -121,9 +122,12 @@ public class DynamicSystemInstallationService extends Service private DynamicSystemManager mDynSystem; private NotificationManager mNM; - private long mSystemSize; - private long mUserdataSize; - private long mInstalledSize; + private int mNumInstalledPartitions; + + private String mCurrentPartitionName; + private long mCurrentPartitionSize; + private long mCurrentPartitionInstalledSize; + private boolean mJustCancelledByUser; // This is for testing only now @@ -176,8 +180,12 @@ public class DynamicSystemInstallationService extends Service } @Override - public void onProgressUpdate(long installedSize) { - mInstalledSize = installedSize; + public void onProgressUpdate(InstallationAsyncTask.Progress progress) { + mCurrentPartitionName = progress.mPartitionName; + mCurrentPartitionSize = progress.mPartitionSize; + mCurrentPartitionInstalledSize = progress.mInstalledSize; + mNumInstalledPartitions = progress.mNumInstalledPartitions; + postStatus(STATUS_IN_PROGRESS, CAUSE_NOT_SPECIFIED, null); } @@ -197,11 +205,16 @@ public class DynamicSystemInstallationService extends Service resetTaskAndStop(); switch (result) { + case RESULT_CANCELLED: + postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED, null); + break; + case RESULT_ERROR_IO: postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_IO, detail); 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); 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) { if (!verifyRequest(intent)) { Log.e(TAG, "Verification failed. Did you use VerificationActivity?"); @@ -234,12 +241,13 @@ public class DynamicSystemInstallationService extends Service } String url = intent.getDataString(); - mSystemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0); - mUserdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0); + long systemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0); + long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0); mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false); + // TODO: better constructor or builder mInstallTask = new InstallationAsyncTask( - url, mSystemSize, mUserdataSize, this, mDynSystem, this); + url, systemSize, userdataSize, this, mDynSystem, this); mInstallTask.execute(); @@ -257,7 +265,7 @@ public class DynamicSystemInstallationService extends Service mJustCancelledByUser = true; 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"); } else { Log.e(TAG, "Trying to cancel installation while it's already completed."); @@ -288,7 +296,7 @@ public class DynamicSystemInstallationService extends Service private void executeRebootToDynSystemCommand() { boolean enabled = false; - if (mInstallTask != null && mInstallTask.getResult() == RESULT_OK) { + if (mInstallTask != null && mInstallTask.isCompleted()) { enabled = mInstallTask.commit(); } else if (isDynamicSystemInstalled()) { enabled = mDynSystem.setEnable(true, true); @@ -380,8 +388,16 @@ public class DynamicSystemInstallationService extends Service case STATUS_IN_PROGRESS: builder.setContentText(getString(R.string.notification_install_inprogress)); - int max = (int) Math.max((mSystemSize + mUserdataSize) >> 20, 1); - int progress = (int) (mInstalledSize >> 20); + int max = 1024; + 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); @@ -464,7 +480,8 @@ public class DynamicSystemInstallationService extends Service throws RemoteException { 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) { bundle.putSerializable(DynamicSystemClient.KEY_EXCEPTION_DETAIL, @@ -492,9 +509,7 @@ public class DynamicSystemInstallationService extends Service return STATUS_IN_PROGRESS; case FINISHED: - int result = mInstallTask.getResult(); - - if (result == RESULT_OK) { + if (mInstallTask.isCompleted()) { return STATUS_READY; } else { throw new IllegalStateException("A failed InstallationTask is not reset"); diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java index 19ae97070188..b206a1fccba4 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java @@ -17,7 +17,6 @@ package com.android.dynsystem; import android.content.Context; -import android.gsi.GsiProgress; import android.net.Uri; import android.os.AsyncTask; import android.os.MemoryFile; @@ -27,35 +26,70 @@ import android.util.Log; import android.webkit.URLUtil; import java.io.BufferedInputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; import java.util.Locale; 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 int READ_BUFFER_SIZE = 1 << 13; + private static final long MIN_PROGRESS_TO_PUBLISH = 1 << 27; - private class InvalidImageUrlException extends RuntimeException { - private InvalidImageUrlException(String message) { + private static final List<String> UNSUPPORTED_PARTITIONS = + Arrays.asList("vbmeta", "boot", "userdata", "dtbo", "super_empty", "system_other"); + + private class UnsupportedUrlException extends RuntimeException { + private UnsupportedUrlException(String message) { + super(message); + } + } + + private class UnsupportedFormatException extends RuntimeException { + private UnsupportedFormatException(String message) { super(message); } } - /** Not completed, including being cancelled */ - static final int NO_RESULT = 0; + /** UNSET means the installation is not completed */ + static final int RESULT_UNSET = 0; static final int RESULT_OK = 1; - static final int RESULT_ERROR_IO = 2; - static final int RESULT_ERROR_INVALID_URL = 3; + static final int RESULT_CANCELLED = 2; + 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; - interface InstallStatusListener { - void onProgressUpdate(long installedSize); + class Progress { + 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 onCancelled(); } private final String mUrl; @@ -63,16 +97,17 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { private final long mUserdataSize; private final Context mContext; private final DynamicSystemManager mDynSystem; - private final InstallStatusListener mListener; + private final ProgressListener mListener; private DynamicSystemManager.Session mInstallationSession; - private int mResult = NO_RESULT; + private boolean mIsZip; + private boolean mIsCompleted; private InputStream mStream; - + private ZipFile mZipFile; InstallationAsyncTask(String url, long systemSize, long userdataSize, Context context, - DynamicSystemManager dynSystem, InstallStatusListener listener) { + DynamicSystemManager dynSystem, ProgressListener listener) { mUrl = url; mSystemSize = systemSize; mUserdataSize = userdataSize; @@ -82,133 +117,292 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { } @Override - protected void onPreExecute() { - mListener.onProgressUpdate(0); - } - - @Override protected Throwable doInBackground(String... voids) { Log.d(TAG, "Start doInBackground(), URL: " + mUrl); try { - long installedSize = 0; - long reportedInstalledSize = 0; - - long minStepToReport = (mSystemSize + mUserdataSize) / 100; - - // init input stream before calling startInstallation(), which takes 90 seconds. - initInputStream(); + // call DynamicSystemManager to cleanup stuff + mDynSystem.remove(); - Thread thread = - new Thread( - () -> { - mDynSystem.startInstallation(); - mDynSystem.createPartition("userdata", mUserdataSize, false); - mInstallationSession = - mDynSystem.createPartition("system", mSystemSize, true); - }); + verifyAndPrepare(); - thread.start(); + mDynSystem.startInstallation(); - 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); + installUserdata(); + if (isCancelled()) { + mDynSystem.remove(); + return null; } - if (mInstallationSession == null) { - throw new IOException( - "Failed to start installation with requested size: " - + (mSystemSize + mUserdataSize)); + installImages(); + if (isCancelled()) { + mDynSystem.remove(); + 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(); - return null; - } catch (Exception e) { e.printStackTrace(); + mDynSystem.remove(); return e; } finally { close(); } - } - - @Override - protected void onCancelled() { - Log.d(TAG, "onCancelled(), URL: " + mUrl); - mListener.onCancelled(); + return null; } @Override protected void onPostExecute(Throwable detail) { + int result = RESULT_UNSET; + if (detail == null) { - mResult = RESULT_OK; + result = RESULT_OK; + mIsCompleted = true; } else if (detail instanceof IOException) { - mResult = RESULT_ERROR_IO; - } else if (detail instanceof InvalidImageUrlException) { - mResult = RESULT_ERROR_INVALID_URL; + 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 { - mResult = RESULT_ERROR_EXCEPTION; + result = RESULT_ERROR_EXCEPTION; } - Log.d(TAG, "onPostExecute(), URL: " + mUrl + ", result: " + mResult); + Log.d(TAG, "onPostExecute(), URL: " + mUrl + ", result: " + result); + + mListener.onResult(result, detail); + } + + @Override + protected void onCancelled() { + Log.d(TAG, "onCancelled(), URL: " + mUrl); - mListener.onResult(mResult, detail); + if (mDynSystem.abort()) { + Log.d(TAG, "Installation aborted"); + } else { + Log.w(TAG, "DynamicSystemManager.abort() returned false"); + } + + mListener.onResult(RESULT_CANCELLED, null); } @Override - protected void onProgressUpdate(Long... values) { - long progress = values[0]; + protected void onProgressUpdate(Progress... values) { + Progress progress = values[0]; mListener.onProgressUpdate(progress); } - private void initInputStream() throws IOException, InvalidImageUrlException { - if (URLUtil.isNetworkUrl(mUrl) || URLUtil.isFileUrl(mUrl)) { - mStream = new BufferedInputStream(new GZIPInputStream(new URL(mUrl).openStream())); + private void verifyAndPrepare() throws Exception { + String extension = mUrl.substring(mUrl.lastIndexOf('.') + 1); + + if ("gz".equals(extension) || "gzip".equals(extension)) { + mIsZip = false; + } else if ("zip".equals(extension)) { + mIsZip = true; + } else { + throw new UnsupportedFormatException( + 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)) { - Uri uri = Uri.parse(mUrl); - mStream = new BufferedInputStream(new GZIPInputStream( - mContext.getContentResolver().openInputStream(uri))); + 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 InvalidImageUrlException( - String.format(Locale.US, "Unsupported file source: %s", mUrl)); + 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 = null; } + if (mZipFile != null) { + mZipFile.close(); + mZipFile = null; + } } catch (IOException e) { // ignore } } - int getResult() { - return mResult; + boolean isCompleted() { + return mIsCompleted; } boolean commit() { - if (mInstallationSession == null) { - return false; - } - - return mInstallationSession.commit(); + return mDynSystem.setEnable(true, true); } } |