From 3a9690c3577d85a06ba3db9ac477dfbc18305cfe Mon Sep 17 00:00:00 2001 From: Yi-Yo Chiang Date: Fri, 25 Mar 2022 19:01:31 +0800 Subject: [PATCH] DSU service: Weighted progress bar Weight progress of writable partitions less because they take much less time to complete compared to readonly partitions. This is best effort only and done only when we can deduce the total number of partitions beforehand. Also publish the weighted progress to events log so external tools can use this value directly, instead of having to calculate this complex progress weighting themselves. Revise the events log field name to better reflect the "unit" the metrics are in (size -> bytes, partition -> partition_name). DynamicSystemInstallationService notification progress should use this weighted progress if available. Bug: 169036106 Bug: 224909776 Test: Start a DSU installation and observe the logcat and system notification Change-Id: I81561c1994698387b275cab36ee058dd581f9879 --- .../DynamicSystemInstallationService.java | 62 +++++--- .../android/dynsystem/EventLogTags.logtags | 2 +- .../dynsystem/InstallationAsyncTask.java | 142 +++++++++++++----- 3 files changed, 146 insertions(+), 60 deletions(-) diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index 02128d4806789..98d4cf0d1182e 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -112,18 +112,20 @@ public class DynamicSystemInstallationService extends Service private static final int EVENT_DSU_INSTALL_FAILED = 120002; protected static void logEventProgressUpdate( - String partition, - long installedSize, - long partitionSize, + String partitionName, + long installedBytes, + long totalBytes, int partitionNumber, - int totalPartitionNumber) { + int totalPartitionNumber, + int totalProgressPercentage) { EventLog.writeEvent( EVENT_DSU_PROGRESS_UPDATE, - partition, - installedSize, - partitionSize, + partitionName, + installedBytes, + totalBytes, partitionNumber, - totalPartitionNumber); + totalPartitionNumber, + totalProgressPercentage); } protected static void logEventComplete() { @@ -231,10 +233,11 @@ public class DynamicSystemInstallationService extends Service public void onProgressUpdate(InstallationAsyncTask.Progress progress) { logEventProgressUpdate( progress.partitionName, - progress.installedSize, - progress.partitionSize, + progress.installedBytes, + progress.totalBytes, progress.partitionNumber, - progress.totalPartitionNumber); + progress.totalPartitionNumber, + progress.totalProgressPercentage); mInstallTaskProgress = progress; postStatus(STATUS_IN_PROGRESS, CAUSE_NOT_SPECIFIED, null); @@ -486,20 +489,28 @@ public class DynamicSystemInstallationService extends Service builder.setContentText(getString(R.string.notification_install_inprogress)); if (mInstallTaskProgress != null) { - int max = 1024; - int progress = 0; + if (mInstallTaskProgress.totalPartitionNumber > 0) { + // totalProgressPercentage is defined iff totalPartitionNumber is defined + builder.setProgress( + 100, + mInstallTaskProgress.totalProgressPercentage, + /* indeterminate = */ false); + } else { + int max = 1024; + int progress = 0; - int currentMax = max >> mInstallTaskProgress.partitionNumber; - progress = max - currentMax * 2; + int currentMax = max >> mInstallTaskProgress.partitionNumber; + progress = max - currentMax * 2; - long currentProgress = - (mInstallTaskProgress.installedSize >> 20) - * currentMax - / Math.max(mInstallTaskProgress.partitionSize >> 20, 1); + long currentProgress = + (mInstallTaskProgress.installedBytes >> 20) + * currentMax + / Math.max(mInstallTaskProgress.totalBytes >> 20, 1); - progress += (int) currentProgress; + progress += (int) currentProgress; - builder.setProgress(max, progress, false); + builder.setProgress(max, progress, false); + } } builder.addAction(new Notification.Action.Builder( null, getString(R.string.notification_action_cancel), @@ -609,10 +620,11 @@ public class DynamicSystemInstallationService extends Service if (status == STATUS_IN_PROGRESS && mInstallTaskProgress != null) { msg.append( String.format( - ", partition name: %s, progress: %d/%d", + ", partition name: %s, progress: %d/%d, total_progress: %d%%", mInstallTaskProgress.partitionName, - mInstallTaskProgress.installedSize, - mInstallTaskProgress.partitionSize)); + mInstallTaskProgress.installedBytes, + mInstallTaskProgress.totalBytes, + mInstallTaskProgress.totalProgressPercentage)); } if (detail != null) { msg.append(", detail: " + detail); @@ -639,7 +651,7 @@ public class DynamicSystemInstallationService extends Service // TODO: send more info to the clients if (mInstallTaskProgress != null) { bundle.putLong( - DynamicSystemClient.KEY_INSTALLED_SIZE, mInstallTaskProgress.installedSize); + DynamicSystemClient.KEY_INSTALLED_SIZE, mInstallTaskProgress.installedBytes); } if (detail != null) { diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/EventLogTags.logtags b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/EventLogTags.logtags index eae9de937700d..407314384be85 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/EventLogTags.logtags +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/EventLogTags.logtags @@ -2,6 +2,6 @@ option java_package com.android.dynsystem -120000 dsu_progress_update (partition|3),(installed_size|2|5),(partition_size|2|5),(partition_number|1|5),(total_partition_number|1|5) +120000 dsu_progress_update (partition_name|3),(installed_bytes|2|5),(total_bytes|2|5),(partition_number|1|5),(total_partition_number|1|5),(total_progress_percentage|1|5) 120001 dsu_install_complete 120002 dsu_install_failed (cause|3) diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java index 998aeeab4b474..2af7e00f9128a 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java @@ -113,22 +113,25 @@ class InstallationAsyncTask extends AsyncTask { static class Progress { public final String partitionName; - public final long installedSize; - public final long partitionSize; + public final long installedBytes; + public final long totalBytes; public final int partitionNumber; public final int totalPartitionNumber; + public final int totalProgressPercentage; Progress( String partitionName, - long installedSize, - long partitionSize, + long installedBytes, + long totalBytes, int partitionNumber, - int totalPartitionNumber) { + int totalPartitionNumber, + int totalProgressPercentage) { this.partitionName = partitionName; - this.installedSize = installedSize; - this.partitionSize = partitionSize; + this.installedBytes = installedBytes; + this.totalBytes = totalBytes; this.partitionNumber = partitionNumber; this.totalPartitionNumber = totalPartitionNumber; + this.totalProgressPercentage = totalProgressPercentage; } } @@ -149,20 +152,28 @@ class InstallationAsyncTask extends AsyncTask { private final ProgressListener mListener; private final boolean mIsNetworkUrl; private final boolean mIsDeviceBootloaderUnlocked; + private final boolean mWantScratchPartition; private DynamicSystemManager.Session mInstallationSession; private KeyRevocationList mKeyRevocationList; private boolean mIsZip; private boolean mIsCompleted; - - private String mPartitionName; - private long mPartitionSize; - private int mPartitionNumber; - private int mTotalPartitionNumber; - 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, @@ -193,21 +204,18 @@ class InstallationAsyncTask extends AsyncTask { (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); - final boolean wantScratchPartition = Build.IS_DEBUGGABLE; try { // call DynamicSystemManager to cleanup stuff mDynSystem.remove(); verifyAndPrepare(); - if (wantScratchPartition) { - ++mTotalPartitionNumber; - } mDynSystem.startInstallation(mDsuSlot); @@ -226,7 +234,7 @@ class InstallationAsyncTask extends AsyncTask { return null; } - if (wantScratchPartition) { + if (mWantScratchPartition) { // If host is debuggable, then install a scratch partition so that we can do // adb remount in the guest system. try { @@ -290,14 +298,54 @@ class InstallationAsyncTask extends AsyncTask { } @Override - protected void onProgressUpdate(Long... installedSize) { + 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( - mPartitionName, - installedSize[0], - mPartitionSize, - mPartitionNumber, - mTotalPartitionNumber)); + 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 { @@ -314,16 +362,12 @@ class InstallationAsyncTask extends AsyncTask { throw new UnsupportedFormatException( String.format(Locale.US, "Unsupported file format: %s", mUrl)); } - // At least two partitions, {system, userdata} - mTotalPartitionNumber = 2; if (mIsNetworkUrl) { mStream = new URL(mUrl).openStream(); } else if (URLUtil.isFileUrl(mUrl)) { if (mIsZip) { mZipFile = new ZipFile(new File(new URL(mUrl).toURI())); - // {*.img in zip} + {userdata} - mTotalPartitionNumber = calculateNumberOfImagesInLocalZip(mZipFile) + 1; } else { mStream = new URL(mUrl).openStream(); } @@ -334,6 +378,32 @@ class InstallationAsyncTask extends AsyncTask { 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)); @@ -370,9 +440,7 @@ class InstallationAsyncTask extends AsyncTask { } }; - mPartitionName = partitionName; - mPartitionSize = partitionSize; - ++mPartitionNumber; + initPartitionProgress(partitionName, partitionSize, /* readonly = */ false); publishProgress(/* installedSize = */ 0L); long prevInstalledSize = 0; @@ -396,6 +464,10 @@ class InstallationAsyncTask extends AsyncTask { } } + if (prevInstalledSize != partitionSize) { + publishProgress(partitionSize); + } + if (mInstallationSession == null) { throw new IOException( "Failed to start installation with requested size: " + partitionSize); @@ -559,9 +631,7 @@ class InstallationAsyncTask extends AsyncTask { mInstallationSession.setAshmem(pfd, memoryFile.length()); - mPartitionName = partitionName; - mPartitionSize = partitionSize; - ++mPartitionNumber; + initPartitionProgress(partitionName, partitionSize, /* readonly = */ true); publishProgress(/* installedSize = */ 0L); long prevInstalledSize = 0; @@ -588,6 +658,10 @@ class InstallationAsyncTask extends AsyncTask { } } + if (prevInstalledSize != partitionSize) { + publishProgress(partitionSize); + } + AvbPublicKey avbPublicKey = new AvbPublicKey(); if (!mInstallationSession.getAvbPublicKey(avbPublicKey)) { imageValidationThrowOrWarning(new PublicKeyException("getAvbPublicKey() failed"));