DSU installation service: Add event log tags

Log important events to the 'events' buffer so other tools can robustly
and progmatically parse the DSU logs.

Bug: 224909776
Test: adb logcat -b events -v descriptive \
  '*:S dsu_progress_update dsu_install_complete dsu_install_failed'
Change-Id: Iac8d3ec31f51eb9410eb631487e26b4ccb0ccf81
diff --git a/packages/DynamicSystemInstallationService/Android.bp b/packages/DynamicSystemInstallationService/Android.bp
index ad86f46..b8f54b3 100644
--- a/packages/DynamicSystemInstallationService/Android.bp
+++ b/packages/DynamicSystemInstallationService/Android.bp
@@ -22,6 +22,9 @@
     defaults: ["platform_app_defaults"],
 
     srcs: ["src/**/*.java"],
+    static_libs: [
+        "DynamicSystemInstallationService-logtags",
+    ],
     resource_dirs: ["res"],
 
     certificate: "platform",
@@ -32,3 +35,8 @@
         enabled: false,
     },
 }
+
+java_library {
+    name: "DynamicSystemInstallationService-logtags",
+    srcs: ["src/**/*.logtags"],
+}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index f8cb5d3..02128d4 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -58,6 +58,7 @@
 import android.os.image.DynamicSystemClient;
 import android.os.image.DynamicSystemManager;
 import android.text.TextUtils;
+import android.util.EventLog;
 import android.util.Log;
 import android.widget.Toast;
 
@@ -104,6 +105,36 @@
     private static final int NOTIFICATION_ID = 1;
 
     /*
+     * Event log tags
+     */
+    private static final int EVENT_DSU_PROGRESS_UPDATE = 120000;
+    private static final int EVENT_DSU_INSTALL_COMPLETE = 120001;
+    private static final int EVENT_DSU_INSTALL_FAILED = 120002;
+
+    protected static void logEventProgressUpdate(
+            String partition,
+            long installedSize,
+            long partitionSize,
+            int partitionNumber,
+            int totalPartitionNumber) {
+        EventLog.writeEvent(
+                EVENT_DSU_PROGRESS_UPDATE,
+                partition,
+                installedSize,
+                partitionSize,
+                partitionNumber,
+                totalPartitionNumber);
+    }
+
+    protected static void logEventComplete() {
+        EventLog.writeEvent(EVENT_DSU_INSTALL_COMPLETE);
+    }
+
+    protected static void logEventFailed(String cause) {
+        EventLog.writeEvent(EVENT_DSU_INSTALL_FAILED, cause);
+    }
+
+    /*
      * IPC
      */
     /** Keeps track of all current registered clients. */
@@ -132,15 +163,10 @@
     private DynamicSystemManager mDynSystem;
     private NotificationManager mNM;
 
-    private int mNumInstalledPartitions;
-
-    private String mCurrentPartitionName;
-    private long mCurrentPartitionSize;
-    private long mCurrentPartitionInstalledSize;
-
     // This is for testing only now
     private boolean mEnableWhenCompleted;
 
+    private InstallationAsyncTask.Progress mInstallTaskProgress;
     private InstallationAsyncTask mInstallTask;
 
 
@@ -203,17 +229,21 @@
 
     @Override
     public void onProgressUpdate(InstallationAsyncTask.Progress progress) {
-        mCurrentPartitionName = progress.partitionName;
-        mCurrentPartitionSize = progress.partitionSize;
-        mCurrentPartitionInstalledSize = progress.installedSize;
-        mNumInstalledPartitions = progress.numInstalledPartitions;
+        logEventProgressUpdate(
+                progress.partitionName,
+                progress.installedSize,
+                progress.partitionSize,
+                progress.partitionNumber,
+                progress.totalPartitionNumber);
 
+        mInstallTaskProgress = progress;
         postStatus(STATUS_IN_PROGRESS, CAUSE_NOT_SPECIFIED, null);
     }
 
     @Override
     public void onResult(int result, Throwable detail) {
         if (result == RESULT_OK) {
+            logEventComplete();
             postStatus(STATUS_READY, CAUSE_INSTALL_COMPLETED, null);
 
             // For testing: enable DSU and restart the device when install completed
@@ -223,6 +253,12 @@
             return;
         }
 
+        if (result == RESULT_CANCELLED) {
+            logEventFailed("Dynamic System installation task is canceled by the user.");
+        } else {
+            logEventFailed("error: " + detail);
+        }
+
         boolean removeNotification = false;
         switch (result) {
             case RESULT_CANCELLED:
@@ -251,16 +287,20 @@
     private void executeInstallCommand(Intent intent) {
         if (!verifyRequest(intent)) {
             Log.e(TAG, "Verification failed. Did you use VerificationActivity?");
+            logEventFailed("VerificationActivity");
             return;
         }
 
         if (mInstallTask != null) {
             Log.e(TAG, "There is already an installation task running");
+            logEventFailed("There is already an ongoing installation task.");
             return;
         }
 
         if (isInDynamicSystem()) {
             Log.e(TAG, "We are already running in DynamicSystem");
+            logEventFailed(
+                    "Cannot start a Dynamic System installation task within a Dynamic System.");
             return;
         }
 
@@ -445,19 +485,22 @@
             case STATUS_IN_PROGRESS:
                 builder.setContentText(getString(R.string.notification_install_inprogress));
 
-                int max = 1024;
-                int progress = 0;
+                if (mInstallTaskProgress != null) {
+                    int max = 1024;
+                    int progress = 0;
 
-                int currentMax = max >> (mNumInstalledPartitions + 1);
-                progress = max - currentMax * 2;
+                    int currentMax = max >> mInstallTaskProgress.partitionNumber;
+                    progress = max - currentMax * 2;
 
-                long currentProgress = (mCurrentPartitionInstalledSize >> 20) * currentMax
-                        / Math.max(mCurrentPartitionSize >> 20, 1);
+                    long currentProgress =
+                            (mInstallTaskProgress.installedSize >> 20)
+                                    * currentMax
+                                    / Math.max(mInstallTaskProgress.partitionSize >> 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),
                         createPendingIntent(ACTION_CANCEL_INSTALL)).build());
@@ -563,13 +606,13 @@
 
         StringBuilder msg = new StringBuilder();
         msg.append("status: " + statusString + ", cause: " + causeString);
-        if (status == STATUS_IN_PROGRESS) {
+        if (status == STATUS_IN_PROGRESS && mInstallTaskProgress != null) {
             msg.append(
                     String.format(
                             ", partition name: %s, progress: %d/%d",
-                            mCurrentPartitionName,
-                            mCurrentPartitionInstalledSize,
-                            mCurrentPartitionSize));
+                            mInstallTaskProgress.partitionName,
+                            mInstallTaskProgress.installedSize,
+                            mInstallTaskProgress.partitionSize));
         }
         if (detail != null) {
             msg.append(", detail: " + detail);
@@ -594,7 +637,10 @@
         Bundle bundle = new Bundle();
 
         // TODO: send more info to the clients
-        bundle.putLong(DynamicSystemClient.KEY_INSTALLED_SIZE, mCurrentPartitionInstalledSize);
+        if (mInstallTaskProgress != null) {
+            bundle.putLong(
+                    DynamicSystemClient.KEY_INSTALLED_SIZE, mInstallTaskProgress.installedSize);
+        }
 
         if (detail != null) {
             bundle.putSerializable(DynamicSystemClient.KEY_EXCEPTION_DETAIL,
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/EventLogTags.logtags b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/EventLogTags.logtags
new file mode 100644
index 0000000..eae9de9
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/EventLogTags.logtags
@@ -0,0 +1,7 @@
+# See system/logging/logcat/event.logtags for a description of the format of this file.
+
+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)
+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 f18d426..b439f84 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -44,7 +44,7 @@
 import java.util.zip.ZipFile;
 import java.util.zip.ZipInputStream;
 
-class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Progress, Throwable> {
+class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> {
 
     private static final String TAG = "InstallationAsyncTask";
 
@@ -106,14 +106,22 @@
 
     static class Progress {
         public final String partitionName;
+        public final long installedSize;
         public final long partitionSize;
-        public final int numInstalledPartitions;
-        public long installedSize;
+        public final int partitionNumber;
+        public final int totalPartitionNumber;
 
-        Progress(String partitionName, long partitionSize, int numInstalledPartitions) {
+        Progress(
+                String partitionName,
+                long installedSize,
+                long partitionSize,
+                int partitionNumber,
+                int totalPartitionNumber) {
             this.partitionName = partitionName;
+            this.installedSize = installedSize;
             this.partitionSize = partitionSize;
-            this.numInstalledPartitions = numInstalledPartitions;
+            this.partitionNumber = partitionNumber;
+            this.totalPartitionNumber = totalPartitionNumber;
         }
     }
 
@@ -139,7 +147,10 @@
     private boolean mIsZip;
     private boolean mIsCompleted;
 
-    private int mNumInstalledPartitions;
+    private String mPartitionName;
+    private long mPartitionSize;
+    private int mPartitionNumber;
+    private int mTotalPartitionNumber;
 
     private InputStream mStream;
     private ZipFile mZipFile;
@@ -175,11 +186,15 @@
     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);
 
@@ -198,7 +213,7 @@
                 return null;
             }
 
-            if (Build.IS_DEBUGGABLE) {
+            if (wantScratchPartition) {
                 // If host is debuggable, then install a scratch partition so that we can do
                 // adb remount in the guest system.
                 try {
@@ -262,9 +277,14 @@
     }
 
     @Override
-    protected void onProgressUpdate(Progress... values) {
-        Progress progress = values[0];
-        mListener.onProgressUpdate(progress);
+    protected void onProgressUpdate(Long... installedSize) {
+        mListener.onProgressUpdate(
+                new Progress(
+                        mPartitionName,
+                        installedSize[0],
+                        mPartitionSize,
+                        mPartitionNumber,
+                        mTotalPartitionNumber));
     }
 
     private void verifyAndPrepare() throws Exception {
@@ -281,12 +301,16 @@
             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();
             }
@@ -333,9 +357,13 @@
             }
         };
 
-        thread.start();
-        Progress progress = new Progress(partitionName, partitionSize, mNumInstalledPartitions++);
+        mPartitionName = partitionName;
+        mPartitionSize = partitionSize;
+        ++mPartitionNumber;
+        publishProgress(/* installedSize = */ 0L);
 
+        long prevInstalledSize = 0;
+        thread.start();
         while (thread.isAlive()) {
             if (isCancelled()) {
                 return;
@@ -343,9 +371,9 @@
 
             final long installedSize = mDynSystem.getInstallationProgress().bytes_processed;
 
-            if (installedSize > progress.installedSize + MIN_PROGRESS_TO_PUBLISH) {
-                progress.installedSize = installedSize;
-                publishProgress(progress);
+            if (installedSize > prevInstalledSize + MIN_PROGRESS_TO_PUBLISH) {
+                publishProgress(installedSize);
+                prevInstalledSize = installedSize;
             }
 
             try {
@@ -392,14 +420,42 @@
         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 IOException, ImageValidationException {
         Log.d(TAG, "To install a streaming ZIP update");
 
         ZipInputStream zis = new ZipInputStream(mStream);
-        ZipEntry zipEntry = null;
+        ZipEntry entry = null;
 
-        while ((zipEntry = zis.getNextEntry()) != null) {
-            installImageFromAnEntry(zipEntry, zis);
+        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;
@@ -414,7 +470,12 @@
 
         while (entries.hasMoreElements()) {
             ZipEntry entry = entries.nextElement();
-            installImageFromAnEntry(entry, mZipFile.getInputStream(entry));
+            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;
@@ -422,28 +483,16 @@
         }
     }
 
-    private boolean installImageFromAnEntry(ZipEntry entry, InputStream is)
+    private void installImageFromAnEntry(ZipEntry entry, InputStream is)
             throws IOException, ImageValidationException {
         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);
-
-        return true;
     }
 
     private void installImage(String partitionName, long uncompressedSize, InputStream is)
@@ -497,8 +546,12 @@
 
         mInstallationSession.setAshmem(pfd, READ_BUFFER_SIZE);
 
-        Progress progress = new Progress(partitionName, partitionSize, mNumInstalledPartitions++);
+        mPartitionName = partitionName;
+        mPartitionSize = partitionSize;
+        ++mPartitionNumber;
+        publishProgress(/* installedSize = */ 0L);
 
+        long prevInstalledSize = 0;
         long installedSize = 0;
         byte[] bytes = new byte[READ_BUFFER_SIZE];
         int numBytesRead;
@@ -516,9 +569,9 @@
 
             installedSize += numBytesRead;
 
-            if (installedSize > progress.installedSize + MIN_PROGRESS_TO_PUBLISH) {
-                progress.installedSize = installedSize;
-                publishProgress(progress);
+            if (installedSize > prevInstalledSize + MIN_PROGRESS_TO_PUBLISH) {
+                publishProgress(installedSize);
+                prevInstalledSize = installedSize;
             }
         }