| /* |
| * 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 android.annotation.NonNull; |
| import android.content.Context; |
| import android.gsi.AvbPublicKey; |
| import android.gsi.IGsiService; |
| import android.net.Uri; |
| import android.os.AsyncTask; |
| import android.os.Build; |
| import android.os.SharedMemory; |
| import android.os.SystemProperties; |
| import android.os.image.DynamicSystemManager; |
| import android.service.persistentdata.PersistentDataBlockManager; |
| import android.system.ErrnoException; |
| import android.util.Log; |
| import android.util.Pair; |
| import android.util.Range; |
| import android.webkit.URLUtil; |
| |
| import org.json.JSONException; |
| |
| import java.io.BufferedInputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URL; |
| import java.nio.ByteBuffer; |
| import java.util.Arrays; |
| import java.util.Enumeration; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| 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> { |
| |
| private static final String TAG = "InstallationAsyncTask"; |
| |
| private static final int MIN_SHARED_MEMORY_SIZE = 8 << 10; // 8KiB |
| private static final int MAX_SHARED_MEMORY_SIZE = 8 << 20; // 8MiB |
| private static final int DEFAULT_SHARED_MEMORY_SIZE = 512 << 10; // 512KiB |
| private static final String SHARED_MEMORY_SIZE_PROP = |
| "dynamic_system.data_transfer.shared_memory.size"; |
| |
| private static final long MIN_PROGRESS_TO_PUBLISH = 1 << 27; |
| |
| private static final List<String> UNSUPPORTED_PARTITIONS = |
| Arrays.asList( |
| "vbmeta", "boot", "userdata", "dtbo", "super_empty", "system_other", "scratch"); |
| |
| private class UnsupportedUrlException extends Exception { |
| private UnsupportedUrlException(String message) { |
| super(message); |
| } |
| } |
| |
| private class UnsupportedFormatException extends Exception { |
| private UnsupportedFormatException(String message) { |
| super(message); |
| } |
| } |
| |
| static class ImageValidationException extends Exception { |
| ImageValidationException(String message) { |
| super(message); |
| } |
| |
| ImageValidationException(Throwable cause) { |
| super(cause); |
| } |
| } |
| |
| static class RevocationListFetchException extends ImageValidationException { |
| RevocationListFetchException(Throwable cause) { |
| super(cause); |
| } |
| } |
| |
| static class KeyRevokedException extends ImageValidationException { |
| KeyRevokedException(String message) { |
| super(message); |
| } |
| } |
| |
| static class PublicKeyException extends ImageValidationException { |
| PublicKeyException(String message) { |
| super(message); |
| } |
| } |
| |
| static class InsufficientSpaceException extends IOException { |
| InsufficientSpaceException(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_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; |
| |
| static class Progress { |
| public final String partitionName; |
| public final long installedBytes; |
| public final long totalBytes; |
| public final int partitionNumber; |
| public final int totalPartitionNumber; |
| public final int totalProgressPercentage; |
| |
| Progress( |
| String partitionName, |
| long installedBytes, |
| long totalBytes, |
| int partitionNumber, |
| int totalPartitionNumber, |
| int totalProgressPercentage) { |
| this.partitionName = partitionName; |
| this.installedBytes = installedBytes; |
| this.totalBytes = totalBytes; |
| this.partitionNumber = partitionNumber; |
| this.totalPartitionNumber = totalPartitionNumber; |
| this.totalProgressPercentage = totalProgressPercentage; |
| } |
| } |
| |
| interface ProgressListener { |
| void onProgressUpdate(Progress progress); |
| |
| void onResult(int resultCode, Throwable detail); |
| } |
| |
| private static class MappedMemoryBuffer implements AutoCloseable { |
| public ByteBuffer mBuffer; |
| |
| MappedMemoryBuffer(@NonNull ByteBuffer buffer) { |
| mBuffer = buffer; |
| } |
| |
| @Override |
| public void close() { |
| if (mBuffer != null) { |
| SharedMemory.unmap(mBuffer); |
| mBuffer = null; |
| } |
| } |
| } |
| |
| private final int mSharedMemorySize; |
| private final String mUrl; |
| private final String mDsuSlot; |
| private final String mPublicKey; |
| private final long mSystemSize; |
| private final long mUserdataSize; |
| private final Context mContext; |
| private final DynamicSystemManager mDynSystem; |
| private final ProgressListener mListener; |
| private final boolean mIsNetworkUrl; |
| private final boolean mIsDeviceBootloaderUnlocked; |
| private final boolean mWantScratchPartition; |
| private int mCreatePartitionStatus; |
| private DynamicSystemManager.Session mInstallationSession; |
| private KeyRevocationList mKeyRevocationList; |
| |
| private boolean mIsZip; |
| private boolean mIsCompleted; |
| private InputStream mStream; |
| private ZipFile mZipFile; |
| |
| private static final double PROGRESS_READONLY_PARTITION_WEIGHT = 0.8; |
| private static final double PROGRESS_WRITABLE_PARTITION_WEIGHT = 0.2; |
| |
| private String mProgressPartitionName; |
| private long mProgressTotalBytes; |
| private int mProgressPartitionNumber; |
| private boolean mProgressPartitionIsReadonly; |
| private int mProgressCompletedReadonlyPartitions; |
| private int mProgressCompletedWritablePartitions; |
| private int mTotalReadonlyPartitions; |
| private int mTotalWritablePartitions; |
| private int mTotalPartitionNumber; |
| |
| InstallationAsyncTask( |
| String url, |
| String dsuSlot, |
| String publicKey, |
| long systemSize, |
| long userdataSize, |
| Context context, |
| DynamicSystemManager dynSystem, |
| ProgressListener listener) { |
| mSharedMemorySize = |
| Range.create(MIN_SHARED_MEMORY_SIZE, MAX_SHARED_MEMORY_SIZE) |
| .clamp( |
| SystemProperties.getInt( |
| SHARED_MEMORY_SIZE_PROP, DEFAULT_SHARED_MEMORY_SIZE)); |
| mUrl = url; |
| mDsuSlot = dsuSlot; |
| mPublicKey = publicKey; |
| mSystemSize = systemSize; |
| mUserdataSize = userdataSize; |
| mContext = context; |
| mDynSystem = dynSystem; |
| mListener = listener; |
| mIsNetworkUrl = URLUtil.isNetworkUrl(mUrl); |
| PersistentDataBlockManager pdbManager = |
| (PersistentDataBlockManager) |
| mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE); |
| mIsDeviceBootloaderUnlocked = |
| (pdbManager != null) |
| && (pdbManager.getFlashLockState() |
| == PersistentDataBlockManager.FLASH_LOCK_UNLOCKED); |
| mWantScratchPartition = Build.IS_DEBUGGABLE; |
| } |
| |
| @Override |
| protected Throwable doInBackground(String... voids) { |
| Log.d(TAG, "Start doInBackground(), URL: " + mUrl); |
| |
| try { |
| // call DynamicSystemManager to cleanup stuff |
| mDynSystem.remove(); |
| |
| verifyAndPrepare(); |
| |
| mDynSystem.startInstallation(mDsuSlot); |
| |
| installUserdata(); |
| if (isCancelled()) { |
| mDynSystem.remove(); |
| return null; |
| } |
| if (mUrl == null) { |
| mDynSystem.finishInstallation(); |
| return null; |
| } |
| installImages(); |
| if (isCancelled()) { |
| mDynSystem.remove(); |
| return null; |
| } |
| |
| if (mWantScratchPartition) { |
| // If host is debuggable, then install a scratch partition so that we can do |
| // adb remount in the guest system. |
| try { |
| installScratch(); |
| } catch (IOException e) { |
| // Failing to install overlayFS scratch shouldn't be fatal. |
| // Just ignore the error and skip installing the scratch partition. |
| Log.w(TAG, e.toString(), e); |
| } |
| if (isCancelled()) { |
| mDynSystem.remove(); |
| return null; |
| } |
| } |
| |
| mDynSystem.finishInstallation(); |
| } catch (Exception e) { |
| Log.e(TAG, e.toString(), e); |
| mDynSystem.remove(); |
| return e; |
| } finally { |
| 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 |
| protected void onCancelled() { |
| Log.d(TAG, "onCancelled(), URL: " + mUrl); |
| |
| 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... progress) { |
| final long installedBytes = progress[0]; |
| int totalProgressPercentage = 0; |
| if (mTotalPartitionNumber > 0) { |
| final double readonlyPartitionWeight = |
| mTotalReadonlyPartitions > 0 |
| ? PROGRESS_READONLY_PARTITION_WEIGHT / mTotalReadonlyPartitions |
| : 0; |
| final double writablePartitionWeight = |
| mTotalWritablePartitions > 0 |
| ? PROGRESS_WRITABLE_PARTITION_WEIGHT / mTotalWritablePartitions |
| : 0; |
| double totalProgress = 0.0; |
| if (mProgressTotalBytes > 0) { |
| totalProgress += |
| (mProgressPartitionIsReadonly |
| ? readonlyPartitionWeight |
| : writablePartitionWeight) |
| * installedBytes |
| / mProgressTotalBytes; |
| } |
| totalProgress += readonlyPartitionWeight * mProgressCompletedReadonlyPartitions; |
| totalProgress += writablePartitionWeight * mProgressCompletedWritablePartitions; |
| totalProgressPercentage = (int) (totalProgress * 100); |
| } |
| mListener.onProgressUpdate( |
| new Progress( |
| mProgressPartitionName, |
| installedBytes, |
| mProgressTotalBytes, |
| mProgressPartitionNumber, |
| mTotalPartitionNumber, |
| totalProgressPercentage)); |
| } |
| |
| private void initPartitionProgress(String partitionName, long totalBytes, boolean readonly) { |
| if (mProgressPartitionNumber > 0) { |
| // Assume previous partition completed successfully. |
| if (mProgressPartitionIsReadonly) { |
| ++mProgressCompletedReadonlyPartitions; |
| } else { |
| ++mProgressCompletedWritablePartitions; |
| } |
| } |
| mProgressPartitionName = partitionName; |
| mProgressTotalBytes = totalBytes; |
| mProgressPartitionIsReadonly = readonly; |
| ++mProgressPartitionNumber; |
| } |
| |
| private void verifyAndPrepare() throws Exception { |
| if (mUrl == null) { |
| return; |
| } |
| 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 (mIsNetworkUrl) { |
| 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)); |
| } |
| |
| boolean hasTotalPartitionNumber = false; |
| if (mIsZip) { |
| if (mZipFile != null) { |
| // {*.img in zip} + {userdata} |
| hasTotalPartitionNumber = true; |
| mTotalReadonlyPartitions = calculateNumberOfImagesInLocalZip(mZipFile); |
| mTotalWritablePartitions = 1; |
| } else { |
| // TODO: Come up with a way to retrieve the number of total partitions from |
| // network URL. |
| } |
| } else { |
| // gzip has exactly two partitions, {system, userdata} |
| hasTotalPartitionNumber = true; |
| mTotalReadonlyPartitions = 1; |
| mTotalWritablePartitions = 1; |
| } |
| |
| if (hasTotalPartitionNumber) { |
| if (mWantScratchPartition) { |
| // {scratch} |
| ++mTotalWritablePartitions; |
| } |
| mTotalPartitionNumber = mTotalReadonlyPartitions + mTotalWritablePartitions; |
| } |
| |
| try { |
| String listUrl = mContext.getString(R.string.key_revocation_list_url); |
| mKeyRevocationList = KeyRevocationList.fromUrl(new URL(listUrl)); |
| } catch (IOException | JSONException e) { |
| mKeyRevocationList = new KeyRevocationList(); |
| imageValidationThrowOrWarning(new RevocationListFetchException(e)); |
| } |
| if (mKeyRevocationList.isRevoked(mPublicKey)) { |
| imageValidationThrowOrWarning(new KeyRevokedException(mPublicKey)); |
| } |
| } |
| |
| private void imageValidationThrowOrWarning(ImageValidationException e) |
| throws ImageValidationException { |
| if (mIsDeviceBootloaderUnlocked || !mIsNetworkUrl) { |
| // If device is OEM unlocked or DSU is being installed from a local file URI, |
| // then be permissive. |
| Log.w(TAG, e.toString()); |
| } else { |
| throw e; |
| } |
| } |
| |
| private void installWritablePartition(final String partitionName, final long partitionSize) |
| throws IOException { |
| Log.d(TAG, "Creating writable partition: " + partitionName + ", size: " + partitionSize); |
| |
| mCreatePartitionStatus = 0; |
| mInstallationSession = null; |
| Thread thread = |
| new Thread() { |
| @Override |
| public void run() { |
| Pair<Integer, DynamicSystemManager.Session> result = |
| mDynSystem.createPartition( |
| partitionName, partitionSize, /* readOnly = */ false); |
| mCreatePartitionStatus = result.first; |
| mInstallationSession = result.second; |
| } |
| }; |
| |
| initPartitionProgress(partitionName, partitionSize, /* readonly = */ false); |
| publishProgress(/* installedSize = */ 0L); |
| |
| long prevInstalledSize = 0; |
| thread.start(); |
| while (thread.isAlive()) { |
| if (isCancelled()) { |
| return; |
| } |
| |
| final long installedSize = mDynSystem.getInstallationProgress().bytes_processed; |
| |
| if (installedSize > prevInstalledSize + MIN_PROGRESS_TO_PUBLISH) { |
| publishProgress(installedSize); |
| prevInstalledSize = installedSize; |
| } |
| |
| try { |
| Thread.sleep(100); |
| } catch (InterruptedException e) { |
| // Ignore the error. |
| } |
| } |
| |
| if (mInstallationSession == null) { |
| if (mCreatePartitionStatus == IGsiService.INSTALL_ERROR_NO_SPACE |
| || mCreatePartitionStatus == IGsiService.INSTALL_ERROR_FILE_SYSTEM_CLUTTERED) { |
| throw new InsufficientSpaceException( |
| "Failed to create " |
| + partitionName |
| + " partition: storage media has insufficient free space"); |
| } else { |
| throw new IOException( |
| "Failed to start installation with requested size: " + partitionSize); |
| } |
| } |
| |
| // Reset installation session and verify that installation completes successfully. |
| mInstallationSession = null; |
| if (!mDynSystem.closePartition()) { |
| throw new IOException("Failed to complete partition installation: " + partitionName); |
| } |
| |
| // Ensure a 100% mark is published. |
| if (prevInstalledSize != partitionSize) { |
| publishProgress(partitionSize); |
| } |
| } |
| |
| private void installScratch() throws IOException { |
| installWritablePartition("scratch", mDynSystem.suggestScratchSize()); |
| } |
| |
| private void installUserdata() throws IOException { |
| installWritablePartition("userdata", mUserdataSize); |
| } |
| |
| private void installImages() throws ExecutionException, IOException, ImageValidationException { |
| if (mStream != null) { |
| if (mIsZip) { |
| installStreamingZipUpdate(); |
| } else { |
| installStreamingGzUpdate(); |
| } |
| } else { |
| installLocalZipUpdate(); |
| } |
| } |
| |
| private void installStreamingGzUpdate() |
| throws ExecutionException, IOException, ImageValidationException { |
| Log.d(TAG, "To install a streaming GZ update"); |
| installImage("system", mSystemSize, new GZIPInputStream(mStream)); |
| } |
| |
| private boolean shouldInstallEntry(String name) { |
| if (!name.endsWith(".img")) { |
| return false; |
| } |
| String partitionName = name.substring(0, name.length() - 4); |
| if (UNSUPPORTED_PARTITIONS.contains(partitionName)) { |
| return false; |
| } |
| return true; |
| } |
| |
| private int calculateNumberOfImagesInLocalZip(ZipFile zipFile) { |
| int total = 0; |
| Enumeration<? extends ZipEntry> entries = zipFile.entries(); |
| while (entries.hasMoreElements()) { |
| ZipEntry entry = entries.nextElement(); |
| if (shouldInstallEntry(entry.getName())) { |
| ++total; |
| } |
| } |
| return total; |
| } |
| |
| private void installStreamingZipUpdate() |
| throws ExecutionException, IOException, ImageValidationException { |
| Log.d(TAG, "To install a streaming ZIP update"); |
| |
| ZipInputStream zis = new ZipInputStream(mStream); |
| ZipEntry entry = null; |
| |
| while ((entry = zis.getNextEntry()) != null) { |
| String name = entry.getName(); |
| if (shouldInstallEntry(name)) { |
| installImageFromAnEntry(entry, zis); |
| } else { |
| Log.d(TAG, name + " installation is not supported, skip it."); |
| } |
| |
| if (isCancelled()) { |
| break; |
| } |
| } |
| } |
| |
| private void installLocalZipUpdate() |
| throws ExecutionException, IOException, ImageValidationException { |
| Log.d(TAG, "To install a local ZIP update"); |
| |
| Enumeration<? extends ZipEntry> entries = mZipFile.entries(); |
| |
| while (entries.hasMoreElements()) { |
| ZipEntry entry = entries.nextElement(); |
| String name = entry.getName(); |
| if (shouldInstallEntry(name)) { |
| installImageFromAnEntry(entry, mZipFile.getInputStream(entry)); |
| } else { |
| Log.d(TAG, name + " installation is not supported, skip it."); |
| } |
| |
| if (isCancelled()) { |
| break; |
| } |
| } |
| } |
| |
| private void installImageFromAnEntry(ZipEntry entry, InputStream is) |
| throws ExecutionException, IOException, ImageValidationException { |
| String name = entry.getName(); |
| |
| Log.d(TAG, "ZipEntry: " + name); |
| |
| String partitionName = name.substring(0, name.length() - 4); |
| long uncompressedSize = entry.getSize(); |
| |
| installImage(partitionName, uncompressedSize, is); |
| } |
| |
| private void installImage(String partitionName, long uncompressedSize, InputStream is) |
| throws ExecutionException, IOException, ImageValidationException { |
| |
| 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); |
| } |
| |
| mCreatePartitionStatus = 0; |
| mInstallationSession = null; |
| Thread thread = |
| new Thread() { |
| @Override |
| public void run() { |
| Pair<Integer, DynamicSystemManager.Session> result = |
| mDynSystem.createPartition( |
| partitionName, partitionSize, /* readOnly = */ true); |
| mCreatePartitionStatus = result.first; |
| mInstallationSession = result.second; |
| } |
| }; |
| |
| Log.d(TAG, "Start creating partition: " + partitionName); |
| thread.start(); |
| |
| while (thread.isAlive()) { |
| if (isCancelled()) { |
| return; |
| } |
| |
| try { |
| Thread.sleep(100); |
| } catch (InterruptedException e) { |
| // Ignore the error. |
| } |
| } |
| |
| if (mInstallationSession == null) { |
| if (mCreatePartitionStatus == IGsiService.INSTALL_ERROR_NO_SPACE |
| || mCreatePartitionStatus == IGsiService.INSTALL_ERROR_FILE_SYSTEM_CLUTTERED) { |
| throw new InsufficientSpaceException( |
| "Failed to create " |
| + partitionName |
| + " partition: storage media has insufficient free space"); |
| } else { |
| throw new IOException( |
| "Failed to start installation with requested size: " + partitionSize); |
| } |
| } |
| |
| Log.d(TAG, "Start installing: " + partitionName); |
| |
| long prevInstalledSize = 0; |
| try (SharedMemory sharedMemory = |
| SharedMemory.create("dsu_buffer_" + partitionName, mSharedMemorySize); |
| MappedMemoryBuffer mappedBuffer = |
| new MappedMemoryBuffer(sharedMemory.mapReadWrite())) { |
| mInstallationSession.setAshmem(sharedMemory.getFdDup(), sharedMemory.getSize()); |
| |
| initPartitionProgress(partitionName, partitionSize, /* readonly = */ true); |
| publishProgress(/* installedSize = */ 0L); |
| |
| long installedSize = 0; |
| byte[] readBuffer = new byte[sharedMemory.getSize()]; |
| ByteBuffer buffer = mappedBuffer.mBuffer; |
| ExecutorService executor = Executors.newSingleThreadExecutor(); |
| Future<Boolean> submitPromise = null; |
| |
| while (true) { |
| final int numBytesRead = sis.read(readBuffer, 0, readBuffer.length); |
| |
| if (submitPromise != null) { |
| // Wait until the previous submit task is complete. |
| while (true) { |
| try { |
| if (!submitPromise.get()) { |
| throw new IOException("Failed submitFromAshmem() to DynamicSystem"); |
| } |
| break; |
| } catch (InterruptedException e) { |
| // Ignore. |
| } |
| } |
| |
| // Publish the progress of the previous submit task. |
| if (installedSize > prevInstalledSize + MIN_PROGRESS_TO_PUBLISH) { |
| publishProgress(installedSize); |
| prevInstalledSize = installedSize; |
| } |
| } |
| |
| // Ensure the previous submit task (submitPromise) is complete before exiting the |
| // loop. |
| if (numBytesRead < 0) { |
| break; |
| } |
| |
| if (isCancelled()) { |
| return; |
| } |
| |
| buffer.position(0); |
| buffer.put(readBuffer, 0, numBytesRead); |
| submitPromise = |
| executor.submit(() -> mInstallationSession.submitFromAshmem(numBytesRead)); |
| |
| // Even though we update the bytes counter here, the actual progress is updated only |
| // after the submit task (submitPromise) is complete. |
| installedSize += numBytesRead; |
| } |
| } catch (ErrnoException e) { |
| e.rethrowAsIOException(); |
| } |
| |
| AvbPublicKey avbPublicKey = new AvbPublicKey(); |
| if (!mInstallationSession.getAvbPublicKey(avbPublicKey)) { |
| imageValidationThrowOrWarning(new PublicKeyException("getAvbPublicKey() failed")); |
| } else { |
| String publicKey = toHexString(avbPublicKey.sha1); |
| if (mKeyRevocationList.isRevoked(publicKey)) { |
| imageValidationThrowOrWarning(new KeyRevokedException(publicKey)); |
| } |
| } |
| |
| // Reset installation session and verify that installation completes successfully. |
| mInstallationSession = null; |
| if (!mDynSystem.closePartition()) { |
| throw new IOException("Failed to complete partition installation: " + partitionName); |
| } |
| |
| // Ensure a 100% mark is published. |
| if (prevInstalledSize != partitionSize) { |
| publishProgress(partitionSize); |
| } |
| } |
| |
| private static String toHexString(byte[] bytes) { |
| StringBuilder sb = new StringBuilder(); |
| for (byte b : bytes) { |
| sb.append(String.format("%02x", b)); |
| } |
| return sb.toString(); |
| } |
| |
| private void close() { |
| try { |
| if (mStream != null) { |
| mStream.close(); |
| mStream = null; |
| } |
| if (mZipFile != null) { |
| mZipFile.close(); |
| mZipFile = null; |
| } |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| |
| boolean isCompleted() { |
| return mIsCompleted; |
| } |
| |
| boolean commit(boolean oneShot) { |
| return mDynSystem.setEnable(true, oneShot); |
| } |
| } |